diff --git a/.gitignore b/.gitignore index ded282e..eaf5758 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ hqtoxbot result +hqtoxbot.save diff --git a/src/hqtoxbot.nim b/src/hqtoxbot.nim index 41ef7f6..ce503a1 100644 --- a/src/hqtoxbot.nim +++ b/src/hqtoxbot.nim @@ -1,22 +1,27 @@ import toxcore -import std/asyncdispatch, std/json, std/httpclient, std/strutils +import std/asyncdispatch, std/base64, std/json, std/httpclient, std/os, std/strutils -const spaceApiUrl = "http://spaceapi.hq.c3d2.de:3000/spaceapi.json" - -const adminIds = [ - toAddress "DF0AC9107E0A30E7201C6832B017AC836FBD1EDAC390EE99B68625D73C3FD929FB47F1872CA4" +const + spaceApiUrl = "http://spaceapi.hq.c3d2.de:3000/spaceapi.json" + saveFileName = "hqtoxbot.save" + adminIds = [ + toAddress "DF0AC9107E0A30E7201C6832B017AC836FBD1EDAC390EE99B68625D73C3FD929FB47F1872CA4" # Emery ] proc bootstrap(bot: Tox) = - const servers = [ ("tox.neuland.technology", "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey ) ] + const servers = [ ("tox.neuland.technology", + "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey)] for host, key in servers.items: bot.bootstrap(host, key) proc addAdmin(bot: Tox; id: Address) = - discard bot.addFriend( - id, "You have been granted administrative rights to " & bot.name) + try: + discard bot.addFriend( + id, "You have been granted administrative rights to " & bot.name) + except ToxError: + discard proc updateStatus(bot: Tox; http: AsyncHttpClient) {.async.} = try: @@ -26,66 +31,120 @@ proc updateStatus(bot: Tox; http: AsyncHttpClient) {.async.} = space = parseJson body status = $(space["status"]) if bot.statusMessage != status: - echo "change status to ", status bot.statusMessage = unescape $(space["status"]) except: - echo "status update failed" bot.statusMessage = "status update failed" -proc setupCallbacks(bot: Tox) = +type Command = enum + doorpass, + help, + invite, + lock, + unlock, + +proc setup(bot: Tox) = let http = newAsyncHttpClient() - addTimer(20*1000, oneshot=false) do (fd: AsyncFD) -> bool: + addTimer(20*1000, oneshot = false) do (fd: AsyncFD) -> bool: asyncCheck updateStatus(bot, http) let schalterClient = newAsyncHttpClient() - schalterClient.headers = newHttpHeaders({ "Authorization": "Basic c3dvcmRmaXNo" }) + schalterClient.headers = newHttpHeaders() - #[ - bot.onConferenceInvite do (friend: Friend; kind: ConferenceType; - cookie: string): - discard bot.join(friend, cookie) - ]# + 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.onFriendMessage do (f: Friend; msg: string; kind: MessageType): - case msg + 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 "" - of "lock": - bot.typing(f, true) - let fut = schalterClient.post("http://schalter.hq.c3d2.de/door/lock") - fut.addCallback do (): - bot.typing(f, false) - if fut.failed: - discard bot.send(f, "lock failed") - else: - discard bot.send(f, fut.read.status) + case parseEnum[Command](words[0].normalize) - of "unlock": - bot.typing(f, true) - let fut = schalterClient.post("http://schalter.hq.c3d2.de/door/unlock") - fut.addCallback do (): - bot.typing(f, false) - if fut.failed: - discard bot.send(f, "unlock failed") - else: - discard bot.send(f, fut.read.status) + 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 unlock: + reply """Unlock the HQ door.""" - else: - discard bot.send(f, """unknown command "$1"""" % msg) + of doorpass: + schalterClient.headers["Authorization"] = + "Basic " & base64.encode(words[1], newline = "") + 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) + except: + reply(getCurrentExceptionMsg()) - for id in adminIds: - bot.addAdmin(id) + of lock: + bot.typing(f, true) + let fut = schalterClient.post("http://schalter.hq.c3d2.de/door/lock") + fut.addCallback do (): + bot.typing(f, false) + if fut.failed: + reply("lock failed") + else: + reply(fut.read.status) + + of unlock: + bot.typing(f, true) + let fut = schalterClient.post("http://schalter.hq.c3d2.de/door/unlock") + fut.addCallback do (): + bot.typing(f, false) + if fut.failed: + reply("unlock failed") + else: + reply(fut.read.status) + except: + reply(getCurrentExceptionMsg()) 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.setupCallbacks() + 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: