import toxcore import std/asyncdispatch, std/asyncfile, std/base64, std/json, std/httpclient, std/os, std/strutils, std/uri {.passL: "-lcrypto".} const readmeText = readFile "README.md" spaceApiUrl = "http://spaceapi.hq.c3d2.de:3000/spaceapi.json" shalterLockUrl = "http://172.22.99.204/door/lock" shalterUnlockUrl = "http://172.22.99.204/door/unlock" # These are IP addresses so the door can be unlocked without DNS saveFileName = "hqtoxbot.save" iconFilename = "avatar.png" adminIds = [ toAddress "DF0AC9107E0A30E7201C6832B017AC836FBD1EDAC390EE99B68625D73C3FD929FB47F1872CA4" # Emery ] proc bootstrap(bot: Tox) = const servers = [ ("tox.neuland.technology", "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey ) ] for host, key in servers.items: bot.bootstrap(host, key) proc addAdmin(bot: Tox; id: Address) = try: discard bot.addFriend( id, "You have been granted administrative rights to " & bot.name) except ToxError: discard proc sendAvatar(bot: Tox; friend: Friend) = discard bot.send( friend, TOX_FILE_KIND_AVATAR.uint32, iconFilename.getFileSize, iconFilename) proc sendAvatarChunk(bot: Tox; friend: Friend; file: FileTransfer; pos: uint64; size: int) {.async.} = let iconFile = openAsync(iconFilename, fmRead) iconFile.setFilePos(pos.int64); let chunk = await iconFile.read(size) close iconFile bot.sendChunk(friend, file, pos, chunk) proc updateAvatar(bot: Tox) = assert(iconFilename != "") for friend in bot.friends: if bot.connectionStatus(friend) != TOX_CONNECTION_NONE: bot.sendAvatar(friend) proc updateStatus(bot: Tox) {.async.} = try: let http = newAsyncHttpClient() rsp = await http.get(spaceApiUrl) body = await rsp.body space = parseJson body status = space["status"].getStr open = space["state"]["open"].getBool if bot.statusMessage != status: bot.statusMessage = status if open: bot.status = TOX_USER_STATUS_NONE let iconUrl = space["icon"]["open"].getStr.parseUri await http.downloadFile($iconUrl, iconFilename) else: bot.status = TOX_USER_STATUS_AWAY let iconUrl = space["icon"]["closed"].getStr.parseUri await http.downloadFile($iconUrl, iconFilename) bot.updateAvatar() close http except: echo getCurrentExceptionMsg() bot.statusMessage = "status update failed" type Command = enum doorpass, help, invite, lock, readme, revoke, unlock proc setup(bot: Tox) = addTimer(20*1000, oneshot = false) do (fd: AsyncFD) -> bool: asyncCheck updateStatus(bot) let schalterClient = newAsyncHttpClient() schalterClient.headers = newHttpHeaders() let conference = bot.newConference() `title=`(bot, conference, "C3D2") bot.onFriendConnectionStatus do (f: Friend; status: Connection): if status != TOX_CONNECTION_NONE: bot.invite(f, conference) bot.sendAvatar(f) bot.onFriendMessage do (f: Friend; msg: string; kind: MessageType): proc reply(msg: string) = discard bot.send(f, msg) try: var words = msg.split(' ') if words.len < 1: words.add "help" if words.len < 2: words.add "" case parseEnum[Command](words[0].normalize) of help: var cmd = help if words[1] != "": try: cmd = parseEnum[Command](words[1].normalize) except: reply("$1 is not a help topic" % words[1]) case cmd: of doorpass: reply("""Set the authorization string for opening the door lock. """ & """The authorization string will peristent until the bot is restarted.""") of help: var resp = """Return help message for the following commands:""" for e in Command: resp.add "\n\t" resp.add $e reply(resp) of invite: reply """Invite a new user to the bot.""" of lock: reply("""Lock the HQ door. """ & """If the door is already open his command is not effective.""") of readme: reply """Return bot README""" of revoke: reply """Remove yourself from the bot roster.""" of unlock: reply """Unlock the HQ door.""" of doorpass: schalterClient.headers["Authorization"] = "Basic " & base64.encode(words[1], newline = "") of invite: for id in words[1..words.high]: try: discard bot.addFriend(id.toAddress, "You have been invited to the $1 by $2 ($3)" % [bot.name, bot.name(f), $bot.publicKey(f)]) except: reply(getCurrentExceptionMsg()) of lock: bot.typing(f, true) let fut = schalterClient.post(shalterLockUrl) fut.addCallback do (): bot.typing(f, false) if fut.failed: reply("lock failed") else: reply(fut.read.status) of readme: reply readmeText of revoke: reply """Tchuss""" discard bot.delete(f) of unlock: bot.typing(f, true) let fut = schalterClient.post(shalterUnlockUrl) fut.addCallback do (): bot.typing(f, false) if fut.failed: reply("unlock failed") else: reply(fut.read.status) except: reply(getCurrentExceptionMsg()) bot.onFileChunkRequest do (friend: Friend; file: FileTransfer; pos: uint64; size: int): if size != 0: asyncCheck bot.sendAvatarChunk(friend, file, pos, size) proc newBot(name: string): Tox = result = newTox do (opts: Options): opts.localDiscoveryEnabled = true opts.ipv6Enabled = true if existsFile saveFileName: opts.saveDataType = TOX_SAVEDATA_TYPE_TOX_SAVE opts.savedata = readFile(saveFileName) result.name = name result.setup() for id in adminIds: result.addAdmin(id) result.bootstrap() echo result.name, " is at ", result.address proc main() = let bot = newBot("HQ Bot") writeFile(saveFileName, bot.saveData) echo "DHT port and key: ", bot.udpPort, " ", bot.dhtId while true: iterate bot poll bot.iterationInterval main()