2019-12-17 04:53:51 +01:00
|
|
|
import toxcore
|
|
|
|
|
2020-01-23 15:15:41 +01:00
|
|
|
import std/asyncdispatch, std/asyncfile, std/base64, std/json, std/httpclient,
|
|
|
|
std/os, std/strutils, std/uri
|
2019-12-17 04:53:51 +01:00
|
|
|
|
2020-01-23 15:14:41 +01:00
|
|
|
{.passL: "-lcrypto".}
|
|
|
|
|
2019-12-25 15:40:09 +01:00
|
|
|
const
|
2019-12-26 06:40:15 +01:00
|
|
|
readmeText = readFile "README.md"
|
2019-12-25 15:40:09 +01:00
|
|
|
spaceApiUrl = "http://spaceapi.hq.c3d2.de:3000/spaceapi.json"
|
2020-01-23 15:15:41 +01:00
|
|
|
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
|
2019-12-25 15:40:09 +01:00
|
|
|
saveFileName = "hqtoxbot.save"
|
2020-01-23 15:15:41 +01:00
|
|
|
iconFilename = "avatar.png"
|
2019-12-25 15:40:09 +01:00
|
|
|
adminIds = [
|
|
|
|
toAddress "DF0AC9107E0A30E7201C6832B017AC836FBD1EDAC390EE99B68625D73C3FD929FB47F1872CA4"
|
2019-12-17 04:53:51 +01:00
|
|
|
# Emery
|
|
|
|
]
|
|
|
|
|
2020-01-23 15:15:41 +01:00
|
|
|
|
2019-12-17 04:53:51 +01:00
|
|
|
proc bootstrap(bot: Tox) =
|
2019-12-26 06:40:15 +01:00
|
|
|
const servers = [
|
2020-01-23 15:15:41 +01:00
|
|
|
("tox.neuland.technology",
|
2019-12-26 06:40:15 +01:00
|
|
|
"15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey
|
|
|
|
)
|
|
|
|
]
|
2019-12-17 04:53:51 +01:00
|
|
|
for host, key in servers.items:
|
|
|
|
bot.bootstrap(host, key)
|
|
|
|
|
|
|
|
proc addAdmin(bot: Tox; id: Address) =
|
2019-12-25 15:40:09 +01:00
|
|
|
try:
|
|
|
|
discard bot.addFriend(
|
|
|
|
id, "You have been granted administrative rights to " & bot.name)
|
|
|
|
except ToxError:
|
|
|
|
discard
|
2019-12-17 04:53:51 +01:00
|
|
|
|
2020-01-23 15:15:41 +01:00
|
|
|
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.} =
|
2019-12-23 19:29:15 +01:00
|
|
|
try:
|
|
|
|
let
|
2020-01-23 15:15:41 +01:00
|
|
|
http = newAsyncHttpClient()
|
2019-12-23 19:29:15 +01:00
|
|
|
rsp = await http.get(spaceApiUrl)
|
|
|
|
body = await rsp.body
|
|
|
|
space = parseJson body
|
2020-01-23 15:15:41 +01:00
|
|
|
status = space["status"].getStr
|
|
|
|
open = space["state"]["open"].getBool
|
2019-12-23 19:29:15 +01:00
|
|
|
if bot.statusMessage != status:
|
2020-01-23 15:15:41 +01:00
|
|
|
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
|
2019-12-23 19:29:15 +01:00
|
|
|
except:
|
2020-01-23 15:15:41 +01:00
|
|
|
echo getCurrentExceptionMsg()
|
2019-12-23 19:29:15 +01:00
|
|
|
bot.statusMessage = "status update failed"
|
|
|
|
|
2019-12-25 15:40:09 +01:00
|
|
|
type Command = enum
|
|
|
|
doorpass,
|
|
|
|
help,
|
|
|
|
invite,
|
|
|
|
lock,
|
2019-12-26 06:40:15 +01:00
|
|
|
readme,
|
|
|
|
revoke,
|
2020-01-23 15:15:41 +01:00
|
|
|
unlock
|
2019-12-25 15:40:09 +01:00
|
|
|
|
|
|
|
proc setup(bot: Tox) =
|
|
|
|
addTimer(20*1000, oneshot = false) do (fd: AsyncFD) -> bool:
|
2020-01-23 15:15:41 +01:00
|
|
|
asyncCheck updateStatus(bot)
|
2019-12-23 19:29:15 +01:00
|
|
|
|
|
|
|
let schalterClient = newAsyncHttpClient()
|
2019-12-25 15:40:09 +01:00
|
|
|
schalterClient.headers = newHttpHeaders()
|
2019-12-17 04:53:51 +01:00
|
|
|
|
2019-12-25 15:40:09 +01:00
|
|
|
let conference = bot.newConference()
|
|
|
|
`title=`(bot, conference, "C3D2")
|
|
|
|
|
|
|
|
bot.onFriendConnectionStatus do (f: Friend; status: Connection):
|
|
|
|
if status != TOX_CONNECTION_NONE:
|
|
|
|
bot.invite(f, conference)
|
2020-01-23 15:15:41 +01:00
|
|
|
bot.sendAvatar(f)
|
2019-12-26 06:40:15 +01:00
|
|
|
|
2019-12-23 18:50:36 +01:00
|
|
|
bot.onFriendMessage do (f: Friend; msg: string; kind: MessageType):
|
2019-12-25 15:40:09 +01:00
|
|
|
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.""")
|
2019-12-26 06:40:15 +01:00
|
|
|
of readme:
|
|
|
|
reply """Return bot README"""
|
|
|
|
of revoke:
|
|
|
|
reply """Remove yourself from the bot roster."""
|
2019-12-25 15:40:09 +01:00
|
|
|
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:
|
2020-01-23 15:15:41 +01:00
|
|
|
discard bot.addFriend(id.toAddress,
|
|
|
|
"You have been invited to the $1 by $2 ($3)" % [bot.name,
|
|
|
|
bot.name(f), $bot.publicKey(f)])
|
2019-12-25 15:40:09 +01:00
|
|
|
except:
|
|
|
|
reply(getCurrentExceptionMsg())
|
|
|
|
|
|
|
|
of lock:
|
|
|
|
bot.typing(f, true)
|
2020-01-23 15:15:41 +01:00
|
|
|
let fut = schalterClient.post(shalterLockUrl)
|
2019-12-25 15:40:09 +01:00
|
|
|
fut.addCallback do ():
|
|
|
|
bot.typing(f, false)
|
|
|
|
if fut.failed:
|
|
|
|
reply("lock failed")
|
|
|
|
else:
|
|
|
|
reply(fut.read.status)
|
|
|
|
|
2019-12-26 06:40:15 +01:00
|
|
|
of readme:
|
|
|
|
reply readmeText
|
|
|
|
|
|
|
|
of revoke:
|
|
|
|
reply """Tchuss"""
|
|
|
|
discard bot.delete(f)
|
|
|
|
|
2019-12-25 15:40:09 +01:00
|
|
|
of unlock:
|
|
|
|
bot.typing(f, true)
|
2020-01-23 15:15:41 +01:00
|
|
|
let fut = schalterClient.post(shalterUnlockUrl)
|
2019-12-25 15:40:09 +01:00
|
|
|
fut.addCallback do ():
|
|
|
|
bot.typing(f, false)
|
|
|
|
if fut.failed:
|
|
|
|
reply("unlock failed")
|
|
|
|
else:
|
|
|
|
reply(fut.read.status)
|
|
|
|
except:
|
|
|
|
reply(getCurrentExceptionMsg())
|
2019-12-17 04:53:51 +01:00
|
|
|
|
2020-01-23 15:15:41 +01:00
|
|
|
bot.onFileChunkRequest do (friend: Friend; file: FileTransfer; pos: uint64; size: int):
|
|
|
|
if size != 0:
|
|
|
|
asyncCheck bot.sendAvatarChunk(friend, file, pos, size)
|
|
|
|
|
2019-12-17 04:53:51 +01:00
|
|
|
proc newBot(name: string): Tox =
|
|
|
|
result = newTox do (opts: Options):
|
|
|
|
opts.localDiscoveryEnabled = true
|
|
|
|
opts.ipv6Enabled = true
|
2019-12-25 15:40:09 +01:00
|
|
|
if existsFile saveFileName:
|
|
|
|
opts.saveDataType = TOX_SAVEDATA_TYPE_TOX_SAVE
|
|
|
|
opts.savedata = readFile(saveFileName)
|
|
|
|
|
2019-12-17 04:53:51 +01:00
|
|
|
result.name = name
|
2019-12-25 15:40:09 +01:00
|
|
|
result.setup()
|
|
|
|
for id in adminIds: result.addAdmin(id)
|
2019-12-17 04:53:51 +01:00
|
|
|
result.bootstrap()
|
|
|
|
echo result.name, " is at ", result.address
|
|
|
|
|
|
|
|
proc main() =
|
2019-12-23 19:29:15 +01:00
|
|
|
let bot = newBot("HQ Bot")
|
2019-12-25 15:40:09 +01:00
|
|
|
writeFile(saveFileName, bot.saveData)
|
2019-12-18 05:07:36 +01:00
|
|
|
echo "DHT port and key: ", bot.udpPort, " ", bot.dhtId
|
|
|
|
|
2019-12-17 04:53:51 +01:00
|
|
|
while true:
|
|
|
|
iterate bot
|
|
|
|
poll bot.iterationInterval
|
|
|
|
|
|
|
|
main()
|