diff --git a/.gitignore b/.gitignore index eaf5758..fb75880 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ hqtoxbot result hqtoxbot.save +avatar.png diff --git a/src/hqtoxbot.nim b/src/hqtoxbot.nim index 856059e..8a00ca8 100644 --- a/src/hqtoxbot.nim +++ b/src/hqtoxbot.nim @@ -1,21 +1,27 @@ import toxcore -import std/asyncdispatch, std/base64, std/json, std/httpclient, std/os, std/strutils +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", + ("tox.neuland.technology", "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey ) ] @@ -29,16 +35,48 @@ proc addAdmin(bot: Tox; id: Address) = except ToxError: discard -proc updateStatus(bot: Tox; http: AsyncHttpClient) {.async.} = +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"]) + status = space["status"].getStr + open = space["state"]["open"].getBool if bot.statusMessage != status: - bot.statusMessage = unescape $(space["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 @@ -48,12 +86,11 @@ type Command = enum lock, readme, revoke, - unlock, + unlock proc setup(bot: Tox) = - let http = newAsyncHttpClient() addTimer(20*1000, oneshot = false) do (fd: AsyncFD) -> bool: - asyncCheck updateStatus(bot, http) + asyncCheck updateStatus(bot) let schalterClient = newAsyncHttpClient() schalterClient.headers = newHttpHeaders() @@ -64,9 +101,7 @@ proc setup(bot: Tox) = bot.onFriendConnectionStatus do (f: Friend; status: Connection): if status != TOX_CONNECTION_NONE: bot.invite(f, conference) - - bot.onFriendReadReceipt do (f: friend; id; MessageId): - discard """TODO some commands should be defered until a read receipt is acquired. Maybe just revoke.""" + bot.sendAvatar(f) bot.onFriendMessage do (f: Friend; msg: string; kind: MessageType): proc reply(msg: string) = @@ -114,16 +149,15 @@ proc setup(bot: Tox) = of invite: for id in words[1..words.high]: try: - let - address = id.toAddress - other = bot.addFriend(address, "You have been invited to the $1 by $2 ($3)" % [ bot.name, bot.name(f), $bot.publicKey(f) ]) - bot.invite(other, conference) + 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("http://schalter.hq.c3d2.de/door/lock") + let fut = schalterClient.post(shalterLockUrl) fut.addCallback do (): bot.typing(f, false) if fut.failed: @@ -140,7 +174,7 @@ proc setup(bot: Tox) = of unlock: bot.typing(f, true) - let fut = schalterClient.post("http://schalter.hq.c3d2.de/door/unlock") + let fut = schalterClient.post(shalterUnlockUrl) fut.addCallback do (): bot.typing(f, false) if fut.failed: @@ -150,6 +184,10 @@ proc setup(bot: Tox) = 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