Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
b5fe2aa64d | |||
5986f861c5 | |||
2c1740cd1c | |||
a91c9064ac | |||
ca5fcc734d | |||
3a758e1ab7 | |||
1201257b11 | |||
5d8b8e0f39 | |||
ba4fda62ee | |||
8d06546da6 | |||
9b09fb22fd | |||
89096df226 | |||
0dd148f462 | |||
55addb09d9 | |||
0b5746e48f | |||
cb388b85d6 | |||
6ffe0bb52b | |||
a5f8cf98ea | |||
a8f0a62e33 | |||
f33a6f2e33 | |||
9c6d704d60 | |||
c16a516673 | |||
bf60c1f027 | |||
ac5b9a7f7f | |||
959fdfb36b | |||
c124bfd1e7 | |||
e188d455d7 | |||
2152441358 |
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
################################################################################
|
||||||
|
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
/.vs
|
||||||
|
/out/build/x64-Debug
|
|
@ -1,9 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(p3ng0)
|
project(tourbot)
|
||||||
SET(CMAKE_CXX_STANDARD 17)
|
SET(CMAKE_CXX_STANDARD 17)
|
||||||
SET(CMAKE_CXX_STANDARD_REQUIRED True)
|
SET(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
SET(CMAKE_CXX_FLAGS "-O3")
|
SET(CMAKE_CXX_FLAGS "-O3")
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
find_package(nlohmann_json 3.2.0 REQUIRED)
|
find_package(nlohmann_json 3.2.0 REQUIRED)
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
install(TARGETS p3ng0 RUNTIME DESTINATION bin)
|
install(TARGETS tourbot RUNTIME DESTINATION bin)
|
||||||
|
|
35
CMakeSettings.json
Normal file
35
CMakeSettings.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "x64-Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Linux-GCC-Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"cmakeExecutable": "cmake",
|
||||||
|
"remoteCopySourcesExclusionList": [ ".vs", ".git", "out" ],
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "linux_x64" ],
|
||||||
|
"remoteMachineName": "${defaultRemoteMachineName}",
|
||||||
|
"remoteCMakeListsRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/src",
|
||||||
|
"remoteBuildRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/build/${name}",
|
||||||
|
"remoteInstallRoot": "$HOME/.vs/${projectDirName}/${workspaceHash}/out/install/${name}",
|
||||||
|
"remoteCopySources": true,
|
||||||
|
"rsyncCommandArgs": "-t --delete",
|
||||||
|
"remoteCopyBuildOutput": false,
|
||||||
|
"remoteCopySourcesMethod": "rsync",
|
||||||
|
"variables": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,42 +4,51 @@
|
||||||
messages=conf/messages.list
|
messages=conf/messages.list
|
||||||
# Handles single messages to single phrases.
|
# Handles single messages to single phrases.
|
||||||
replyfile=conf/replies.conf
|
replyfile=conf/replies.conf
|
||||||
# Handles worlds and their names.
|
# Handles worlds and their names. (deprecated)
|
||||||
worldfile=conf/worldlist.conf
|
#worldfile=conf/worldlist.conf
|
||||||
|
# Look-Up Table which maps aliases to the JSON worlds list
|
||||||
|
lutfile=conf/lut.conf
|
||||||
|
|
||||||
# The username you log in as. Account must be registered on worlds servers.
|
# The username you log in as. Account must be registered on worlds servers.
|
||||||
username=Clem
|
username=JohnDoe
|
||||||
|
|
||||||
# The password to the username above.
|
# The password to the username above.
|
||||||
password=hackme
|
password=123456
|
||||||
|
|
||||||
# The username who owns the bot. This is for administrative actions.
|
# The username who owns the bot. This is for administrative actions.
|
||||||
owner=Thom
|
owner=SirGrandpa
|
||||||
|
|
||||||
# The URL to whisper users when asking for bot help.
|
# The URL to whisper users when asking for bot help.
|
||||||
help_url=
|
help_url=
|
||||||
|
|
||||||
# Bot avatar. Must be VIP for articulated to show up.
|
# Bot avatar. Must be VIP for articulated to show up.
|
||||||
avatar=http://files.worlio.com/users/wirlaburla/avatars/pengobot.mov
|
avatar=http://files.worlio.com/users/bonkmaykr/avatarsforme/gabu1s*4h*4v*.mov
|
||||||
|
|
||||||
# Room to appear in.
|
# Room to appear in.
|
||||||
room=GroundZero#Reception<dimension-1>
|
room=GroundZero#Reception<dimension-1>
|
||||||
|
|
||||||
# The position in the world that the bot will sit.
|
# Default world file to "load into"
|
||||||
xpos=0
|
# As Tourbot is a headless client we aren't actually loading anything,
|
||||||
ypos=0
|
# this is solely used to allow users to teleport to the bot.
|
||||||
|
world=http://jett.dacii.net/jett/Recreated%20Worlds/SummersGZ/groundzero%20summers.world
|
||||||
|
|
||||||
|
# The position in the world that the bot will sit when idle.
|
||||||
|
# This should be placed in an easy to reach and clearly visible location.
|
||||||
|
# Locations will be handled on a per-world basis when teleporting to bookmarks.
|
||||||
|
xpos=1293
|
||||||
|
ypos=1710
|
||||||
zpos=0
|
zpos=0
|
||||||
direction=0
|
direction=336
|
||||||
|
|
||||||
# Every 'keep alive', will turn x degrees.
|
# Every 'keep alive', will turn x degrees.
|
||||||
spin=0
|
spin=10
|
||||||
|
|
||||||
# Keep alive interval. Setting this too long will make worlds disconnect you.
|
# Keep alive interval. Setting this too long will make worlds disconnect you.
|
||||||
katime=5
|
katime=5
|
||||||
|
|
||||||
# The time between random messages. The wait period is between minRandomMsgTime and maxRandomMsgTime. Setting these to 0 will disable.
|
# The time between random messages. The wait period is between minRandomMsgTime and maxRandomMsgTime. Setting these to 0 will disable.
|
||||||
minRandomMsgTime=300
|
minRandomMsgTime=300
|
||||||
maxRandomMsgTime=900
|
maxRandomMsgTime=600
|
||||||
|
|
||||||
# Here are all single-definition messages. For anything that isn't picked in a list, it's here.
|
# Here are all single-definition messages. For anything that isn't picked in a list, it's here.
|
||||||
help_msg=You can find more information here: %s
|
help_msg=You can find more information here: %s
|
||||||
|
@ -48,6 +57,7 @@ help_whisper_message=%s, you have been whispered with more information.
|
||||||
time_msg=It is %s.
|
time_msg=It is %s.
|
||||||
roll_msg=%s rolled a %i.
|
roll_msg=%s rolled a %i.
|
||||||
world_not_found_msg=Sorry, I don't know that one.
|
world_not_found_msg=Sorry, I don't know that one.
|
||||||
|
world_found_msg=Taking you there now, teleport to me!
|
||||||
roomusers_msg=There are %i users in this room.
|
roomusers_msg=There are %i users in this room.
|
||||||
conf_reload_msg=My configuration has been reloaded.
|
conf_reload_msg=My configuration has been reloaded.
|
||||||
ping_msg=Response recieved in %ims.
|
ping_msg=Response recieved in %ims.
|
||||||
|
|
4
conf.examples/lut.conf
Normal file
4
conf.examples/lut.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
beachgz=beach
|
||||||
|
beach gz=beach
|
||||||
|
partycave=waterworld
|
||||||
|
party cave=waterworld
|
50
conf.examples/marks.json
Normal file
50
conf.examples/marks.json
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"atlantis": {
|
||||||
|
"name": "Atlantis",
|
||||||
|
"url": "http://files.worlio.com/users/aujourd/atlantis/atlantis.world",
|
||||||
|
"room": "atlantis#beach",
|
||||||
|
"position": [
|
||||||
|
"11544",
|
||||||
|
"7828",
|
||||||
|
"220",
|
||||||
|
"203"
|
||||||
|
],
|
||||||
|
"blacklist": false
|
||||||
|
},
|
||||||
|
"beach": {
|
||||||
|
"name": "Jimbly's Beach",
|
||||||
|
"url": "http://ittraining.net/jimbly/groundzero.world",
|
||||||
|
"room": "Groundzero#Reception",
|
||||||
|
"position": [
|
||||||
|
"500",
|
||||||
|
"500",
|
||||||
|
"150",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"blacklist": true
|
||||||
|
},
|
||||||
|
"mugshots": {
|
||||||
|
"name": "Jimbly's Mugshot Hallway",
|
||||||
|
"url": "http://ittraining.net/jimbly/mugshots.world",
|
||||||
|
"room": "Mugshots#Room0",
|
||||||
|
"position": [
|
||||||
|
"106",
|
||||||
|
"1804",
|
||||||
|
"150",
|
||||||
|
"140"
|
||||||
|
],
|
||||||
|
"blacklist": true
|
||||||
|
},
|
||||||
|
"waterworld": {
|
||||||
|
"name": "Waterworld, aka Party Cave",
|
||||||
|
"url": "http://www.ittraining.net/waterworld/waterworld.world",
|
||||||
|
"room": "Water World#Pool",
|
||||||
|
"position": [
|
||||||
|
"672",
|
||||||
|
"3916",
|
||||||
|
"150",
|
||||||
|
"139"
|
||||||
|
],
|
||||||
|
"blacklist": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,48 @@
|
||||||
[startup]
|
[startup]
|
||||||
Hi, I'm P3NG0, your friendly Worlds bot.
|
*yawn*
|
||||||
My initialization is complete.
|
|
||||||
[attention]
|
[attention]
|
||||||
hey pengo
|
hey tourbot
|
||||||
hey p3ng0
|
hey tourbot
|
||||||
p3ng0
|
tourbot
|
||||||
yo pengo
|
yo tourbot
|
||||||
yo p3ng0
|
yo tourbot
|
||||||
hello p3ng0
|
hello tourbot
|
||||||
aloha p3ng0
|
aloha tourbot
|
||||||
|
[requestverb]
|
||||||
|
give me a
|
||||||
|
give me
|
||||||
|
give me the
|
||||||
|
[requestnoun]
|
||||||
|
take me to
|
||||||
|
take me to a
|
||||||
|
take me to the
|
||||||
|
show me
|
||||||
|
show me a
|
||||||
|
show me the
|
||||||
|
[markreminder]
|
||||||
|
If you like this world, remember to save a Worldsmark!
|
||||||
|
[tourtutorial]
|
||||||
|
Make sure you have "Tourbot" added to your friends list, then click my name and select "Go There" to follow me.
|
||||||
|
[goto]
|
||||||
|
I'm teleporting to %s, follow me!
|
||||||
|
[tour1]
|
||||||
|
Okay, first stop, we are going to %s! Teleport now
|
||||||
|
Get ready! We're on our way to %s! Teleport now
|
||||||
|
[leavingin3]
|
||||||
|
3 minutes left! Make sure to Worldsmark your favorite ones!
|
||||||
|
We'll be going to the next world, %s, in 3 minutes!
|
||||||
|
[leavingin1]
|
||||||
|
1 minute remaining! Get ready to teleport soon!
|
||||||
|
1 minute left! If you aren't done exploring, you should save a Worldsmark now.
|
||||||
|
[leavingsoon]
|
||||||
|
30 seconds, pack your bags!
|
||||||
|
30 seconds left.
|
||||||
|
[tour2]
|
||||||
|
Next stop, we'll be visiting %s! Teleport now
|
||||||
|
Get ready to go to our next stop, %s! Teleport now
|
||||||
|
[tour3]
|
||||||
|
Final stop, we're travelling to %s! Teleport now
|
||||||
|
And finally, we'll be arriving at %s! Teleport now
|
||||||
[greets]
|
[greets]
|
||||||
Hi.
|
Hi.
|
||||||
Hi %s.
|
Hi %s.
|
||||||
|
@ -17,23 +51,32 @@ Nice to meet you %s.
|
||||||
Hope all is well!
|
Hope all is well!
|
||||||
Howdy!
|
Howdy!
|
||||||
Salutations %s.
|
Salutations %s.
|
||||||
Acknowledged.
|
|
||||||
Added '%s' to database.
|
|
||||||
[goodbye]
|
[goodbye]
|
||||||
Goodbye!
|
Later!
|
||||||
|
*fart reverb*
|
||||||
[unknown]
|
[unknown]
|
||||||
Sorry, I don't know what you're asking.
|
Sorry, I don't know what you're asking.
|
||||||
I am unsure of your request.
|
I am unsure of your request.
|
||||||
ERROR: DIVISION BY ZERO
|
Please rephrase.
|
||||||
Need something?
|
|
||||||
[random]
|
[random]
|
||||||
Beep boop!
|
*yawn*
|
||||||
|
[null]
|
||||||
|
Bored? Looking for a new place to go? Artsy? Spooky? Cozy? Say "tourbot, give me a tour"
|
||||||
[jokes]
|
[jokes]
|
||||||
Why did the chicken cross the road? To escape my deadly lasers, of course!
|
The other day, my wife asked me to pass her lipstick, but I accidentally passed her a glue stick. She still isn’t talking to me.
|
||||||
|
The most corrupt CEOs are those of the pretzel companies. They’re always so twisted.
|
||||||
|
As I get older, I remember all the people I lost along the way. Maybe a career as a tour guide was not the right choice.
|
||||||
|
My doctor said I only have 3 weeks to live, so I murdered him and the judge gave me 30 years. Problem solved!
|
||||||
|
I have many jokes about unemployed people — sadly none of them work.
|
||||||
|
You’re not completely useless. You can always serve as a bad example.
|
||||||
|
My boss told me to have a good day. So I went home.
|
||||||
|
I bought a pair of shoes from a drug dealer. I don't know what he laced them with, but I've been tripping all day.
|
||||||
|
Teamwork is important; it helps to put the blame on someone else.
|
||||||
|
The worst part about breaking up with a Japanese woman, is that you have to drop the bomb twice before she understands.
|
||||||
[world]
|
[world]
|
||||||
It's right here: %s
|
It's right here: %s
|
||||||
%s
|
%s
|
||||||
Here is your mark: %s
|
Here is your mark: %s
|
||||||
[whoami]
|
[whoami]
|
||||||
Hi, I'm P3NG0. I'm a friendly bot.
|
I am a BOT designed to help you explore! My source code is based off of P3NG0 by Wirlaburla, we share many of the same functionalities.
|
||||||
I am here to annihilate the human race.
|
I am designed to take you places! Please see https://kangworlds.net/tourbot/ for more information!
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
hi=Hello.
|
hi=Hello.
|
||||||
any tip=No, no tip.
|
any tip=No thanks.
|
||||||
tour=No deal.
|
tour=Where would you like to go?
|
||||||
bye=Goodbye!
|
bye=Goodbye!
|
||||||
cult=There is no cult on Worlds.com.
|
cult=There is no cult on Worlds.com.
|
||||||
lover=You wouldn't make it past my saw blades, much less the lasers.
|
|
||||||
love=I have no concept of love.
|
|
||||||
hate=I have no concept of love.
|
|
||||||
table flip=(╯°□°)╯︵ ┻━┻
|
table flip=(╯°□°)╯︵ ┻━┻
|
||||||
flip a table=(╯°□°)╯︵ ┻━┻
|
flip a table=(╯°□°)╯︵ ┻━┻
|
||||||
(╯°□°)╯︵ ┻━┻=┬─┬ノ( º _ ºノ)
|
(╯°□°)╯︵ ┻━┻=┬─┬ノ( º _ ºノ)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# Deprecated
|
||||||
beach=http://ittraining.net/jimbly/groundzero.world
|
beach=http://ittraining.net/jimbly/groundzero.world
|
||||||
mugshots=http://ittraining.net/jimbly/mugshots.world
|
mugshots=http://ittraining.net/jimbly/mugshots.world
|
||||||
tyler=http://worlio.com/users/dsparil/Tyler%20World/tyler.world
|
tyler=http://worlio.com/users/dsparil/Tyler%20World/tyler.world
|
||||||
|
|
82
readme.md
Normal file
82
readme.md
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# Tourbot
|
||||||
|
### The Friendly Worlds Tour Guide
|
||||||
|
### Progress: Not finished (teleport needs work)
|
||||||
|
![Work in Progress](https://files.worlio.com/users/bonkmaykr/http/git/embed/FaFashionAvenueStage8740underconstruction.gif)
|
||||||
|
Tourbot is an automated chatbot for the Worlds.com online 3D chat service (specifically WorldsPlayer from 1998 and onward). It is based off of P3NG0 by Wirlaburla, the FIRST Worlds chatbot to ever be created and operated live in public. Tourbot retains several features from P3NG0 as well as new features, with the primary selling point being to provide both new and old users an exciting and easy way to explore new worlds, without having to dig through endless archives and file servers like Worlio or jett.dacii.net. This is especially important today as several HOSTs on Worlds have departed or otherwise become inactive, leaving many of the weekly and monthly events abandoned, including tours.
|
||||||
|
|
||||||
|
This project is a gift to the Worlds community, and I owe a special thanks to the endless line of nerds who came before me and reverse-engineered the chat protocol so that this could happen.
|
||||||
|
|
||||||
|
# To-Do
|
||||||
|
Underlined tasks are tasks currently being worked on
|
||||||
|
- [ ] <ins>Core Functions</ins>
|
||||||
|
- [x] Improved configuration system
|
||||||
|
- [ ] Strip out old `worldlist.conf` entirely (pair `where` command with `lookUpWorldName()`)
|
||||||
|
- [ ] <ins>New commands</ins>
|
||||||
|
- [ ] <ins>Teleport-on-request routine</ins>
|
||||||
|
- [ ] Basic Tour Guide routine
|
||||||
|
- [ ] Tour communication (tracking users, prioritize leader commands, whispering users out of range, etc)
|
||||||
|
- [ ] Ability to join another user's ongoing tour
|
||||||
|
- [ ] World Database Building
|
||||||
|
- [ ] Spanish Localization
|
||||||
|
- [ ] Tour Guide Avatar
|
||||||
|
- [ ] Contextual Avatars: on tour
|
||||||
|
- [ ] Contextual Avatars: idle (advertising)
|
||||||
|
- [ ] Help Pages, Website, and Documentation
|
||||||
|
- [ ] Tourbot Status Page
|
||||||
|
- [ ] New User Recognition and Welcome
|
||||||
|
|
||||||
|
# Runtime Requirements
|
||||||
|
- An active IPv4 internet connection
|
||||||
|
- A valid Worlds.com account which you registered using the WorldsPlayer
|
||||||
|
- Linux or any other compatible UNIX derivative
|
||||||
|
- The latest version of glibc
|
||||||
|
- The actual version required will depend on the version of glibc linked during compile time.
|
||||||
|
I compile Tourbot on Arch-based distributions which usually have the latest version.
|
||||||
|
If you use a distro such as Ubuntu or Debian then you may need to compile the bot yourself.
|
||||||
|
- A working brain
|
||||||
|
|
||||||
|
# Building Requirements
|
||||||
|
- CMake
|
||||||
|
- GCC
|
||||||
|
- C++ header/source files from Nlohmann's JSON library
|
||||||
|
- A few Linux distributions have this library available as an installable package. Please check the repo in your package manager.
|
||||||
|
- Otherwise you just need to put the required `json.hpp` file in your include path and make sure CMake and GCC can see it.
|
||||||
|
|
||||||
|
# Building
|
||||||
|
## Linux, MINIX and BSD
|
||||||
|
1. Make sure nlohmann JSON is in your INCLUDE path so your compiler can find it's headers.
|
||||||
|
- On Arch Linux / endeavourOS: `sudo pacman -Sy nlohmann-json`
|
||||||
|
- On Ubuntu / Mint / PopOS: `sudo apt-get install nlohmann-json3-dev`
|
||||||
|
- Other systems: Install and link the library's source files manually.
|
||||||
|
2. Enter the root directory (where `src` is) and run `cmake -S src/ -B bin/` to create the neccessary files to compile the program.
|
||||||
|
3. Run `cmake --build bin/` to create an executable file in the bin folder.
|
||||||
|
4. `cd` to `bin/` and run `./p3ng0`. (Your working directory needs to be the same directory where `conf/` is located.)
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
Tourbot isn't designed for Windows right now. As such, I haven't set up the project files to be compilable on Windows, even with Linux as the target. You can either install WSL and compile there, or set up the compiler yourself if you know what you're doing.
|
||||||
|
|
||||||
|
## Mac
|
||||||
|
I don't use Mac and I never plan to support it, I can't help you.
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
## When are you going to finish this?
|
||||||
|
To quote our lord and savior John Carmack, "It will be done when it's done." I'm the only person working on this and I have a real life (crazy I know).
|
||||||
|
## Can I copy your code?
|
||||||
|
Yes, but you have to keep the GNU GPL 3 license.
|
||||||
|
## Where do I report bugs?
|
||||||
|
Create an issue here on this repository, or send me an email at `bonkmaykr@protonmail.com`.
|
||||||
|
## Can I deploy this myself on Worlds?
|
||||||
|
Yes, but please don't try to clog up the servers or chatrooms with a bunch of redundant bots. If Tourbot is working fine, then be nice and let it do it's job unless you can do it better.
|
||||||
|
I can't stop you from doing whatever you want with it, but if you do bad things your accounts and IP addresses will probably get banned from Worlds, so use Tourbot wisely.
|
||||||
|
## When I try to log in, the console says "Account is no longer valid".
|
||||||
|
Double check your spelling.
|
||||||
|
|
||||||
|
If you know your username is spelled correctly and the account hasn't been deleted/banned, make sure you saved bot.conf with Unix line endings. Windows line endings use a really funny looking two-byte control code for new lines, and Tourbot doesn't understand how to read them yet. It probably thinks that your username is something like "John<68>" instead of "John". I plan to fix this if a Windows port is ever released, but for now, you will need to be careful with your .conf files.
|
||||||
|
|
||||||
|
Many text editors on Windows allow you to change this setting so Tourbot can read your configs correctly, unfortunately the default notepad.exe doesn't include this.
|
||||||
|
## Why isn't there a Windows version?
|
||||||
|
P3NG0 was designed from the very beginning with Linux in mind. When it came time to make Tourbot, the same was true; it's designed to be run headless, 24/7, on a server. Meaning you are expected to have a spare computer handy or a cloud VPS rented in order to run the bot. Just about every sane sysadmin uses Linux for headless systems, since Windows was always optimized around it's user interface and not much else.
|
||||||
|
|
||||||
|
I could make a Windows version, doing so is actually not very difficult. The code for Tourbot is pretty small and it very rarely interacts with system APIs if at all. It's just a matter of if I want to or not.
|
||||||
|
## Why are passwords stored in plaintext? That's not safe!
|
||||||
|
Worlds doesn't have SSL or TLS, doesn't hash passwords, and doesn't have session tokens or revocable sessions. It's a very old program with extremely outdated security. Tourbot isn't secure because the weakest link in the chain makes any security effort on my part pointless. <ins>If you want to remain safe, use a *unique password* for the bot so that it can't be reused in the event it gets stolen.</ins>
|
35
src/client.h
35
src/client.h
|
@ -9,7 +9,7 @@
|
||||||
#include "acfile.h"
|
#include "acfile.h"
|
||||||
#define BUFFERSIZE 4096
|
#define BUFFERSIZE 4096
|
||||||
|
|
||||||
bool debug = false;
|
bool debug = true;
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 rng(rd());
|
std::mt19937 rng(rd());
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ const std::string user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.
|
||||||
std::string confFile = "conf/bot.conf";
|
std::string confFile = "conf/bot.conf";
|
||||||
ACFile* mainConf;
|
ACFile* mainConf;
|
||||||
ACFile* worldlist;
|
ACFile* worldlist;
|
||||||
|
ACFile* marksLUT;
|
||||||
ACFile* replylist;
|
ACFile* replylist;
|
||||||
AMFile* messages;
|
AMFile* messages;
|
||||||
ALFile* filth;
|
ALFile* filth;
|
||||||
|
@ -49,7 +50,7 @@ uint16_t roomport = 5672;
|
||||||
|
|
||||||
std::string login_username;
|
std::string login_username;
|
||||||
std::string login_password;
|
std::string login_password;
|
||||||
std::string avatar = "avatar:pengo.mov";
|
std::string avatar = "http://files.worlio.com/users/bonkmaykr/avatarsforme/gabu1s*4h*4v*.mov";
|
||||||
|
|
||||||
// Needs to include dimension too
|
// Needs to include dimension too
|
||||||
std::string room;
|
std::string room;
|
||||||
|
@ -57,7 +58,7 @@ uint16_t roomID = 1;
|
||||||
|
|
||||||
int protocol = 24;
|
int protocol = 24;
|
||||||
char* version = "1000000000";
|
char* version = "1000000000";
|
||||||
int avatars = 253;
|
int avatars = 253; // avoid culling users if possible... what could go wrong?
|
||||||
int keepAliveTime;
|
int keepAliveTime;
|
||||||
|
|
||||||
uint16_t xPos = 0;
|
uint16_t xPos = 0;
|
||||||
|
@ -70,6 +71,30 @@ std::map<char, char*> properties;
|
||||||
std::map<char, Drone*> objects;
|
std::map<char, Drone*> objects;
|
||||||
std::vector<Group> groups;
|
std::vector<Group> groups;
|
||||||
|
|
||||||
|
namespace internalTypes {
|
||||||
|
struct dronePositionI {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int z;
|
||||||
|
int yaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dronePositionF {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float z;
|
||||||
|
float yaw;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct markEntry {
|
||||||
|
std::string name;
|
||||||
|
std::string url;
|
||||||
|
std::string room;
|
||||||
|
dronePositionI position;
|
||||||
|
bool blacklist;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void loadConfig();
|
void loadConfig();
|
||||||
int deinit(int response);
|
int deinit(int response);
|
||||||
void autoInit();
|
void autoInit();
|
||||||
|
@ -101,6 +126,10 @@ void sendGroupMessage(Group* g, int *sock, std::string message);
|
||||||
void safeDeleteGroupMember(Group* g, std::string member);
|
void safeDeleteGroupMember(Group* g, std::string member);
|
||||||
void qsend(int *sock, unsigned char str[], bool queue);
|
void qsend(int *sock, unsigned char str[], bool queue);
|
||||||
|
|
||||||
|
void handleTeleportRequest(internalTypes::markEntry details);
|
||||||
|
void handleTour(std::string destination[4]);
|
||||||
|
void lookUpWorldName(std::string alias, char* buffer);
|
||||||
|
|
||||||
Drone* getDrone(std::string name) {
|
Drone* getDrone(std::string name) {
|
||||||
for (auto o : objects)
|
for (auto o : objects)
|
||||||
if (o.second->name == name) return o.second;
|
if (o.second->name == name) return o.second;
|
||||||
|
|
203
src/main.cpp
203
src/main.cpp
|
@ -11,6 +11,7 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
//#include <vector>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
#define CURL_STATICLIB
|
#define CURL_STATICLIB
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
@ -32,6 +34,16 @@
|
||||||
#include "props.h"
|
#include "props.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
// Global variables for tour guide
|
||||||
|
bool onTour = false; // whether or not we should process the tour routine
|
||||||
|
std::string tourQueue[4]; // list of world names to teleport to
|
||||||
|
std::string realLocation; // used to allow teleporting from users
|
||||||
|
json worldsmarks; // internal World database loaded from marks.json
|
||||||
|
std::string leader = ""; // whoever requested the current tour
|
||||||
|
int schedule = 0; // compare against system clock for timing
|
||||||
|
int tourStep = 0; // which world we're on
|
||||||
|
|
||||||
|
|
||||||
class QueuedPacket {
|
class QueuedPacket {
|
||||||
public:
|
public:
|
||||||
QueuedPacket() {};
|
QueuedPacket() {};
|
||||||
|
@ -61,9 +73,10 @@ int main(int argc, char const* argv[]) {
|
||||||
printf("info: primary conf file set to \"%s\"\n", confFile.c_str());
|
printf("info: primary conf file set to \"%s\"\n", confFile.c_str());
|
||||||
}
|
}
|
||||||
loadConfig();
|
loadConfig();
|
||||||
/*for (auto const& c : mainConf->get()) {
|
|
||||||
printf("info: (%s),(%s)\n", c.first.c_str(), c.second.c_str());
|
// initialize teleportation ability
|
||||||
}*/
|
realLocation = mainConf->getValue("world", "http://jett.dacii.net/jett/Recreated%20Worlds/SummersGZ/groundzero%20summers.world");
|
||||||
|
|
||||||
autoInit();
|
autoInit();
|
||||||
while (autoOnline || roomOnline) {
|
while (autoOnline || roomOnline) {
|
||||||
while (!roomOnline) {} // We require the room but get disconnected from auto after awhile
|
while (!roomOnline) {} // We require the room but get disconnected from auto after awhile
|
||||||
|
@ -84,6 +97,7 @@ int main(int argc, char const* argv[]) {
|
||||||
void loadConfig() {
|
void loadConfig() {
|
||||||
mainConf = new ACFile(confFile.c_str());
|
mainConf = new ACFile(confFile.c_str());
|
||||||
worldlist = new ACFile(mainConf->getValue("worldfile", "conf/worldlist.conf").c_str());
|
worldlist = new ACFile(mainConf->getValue("worldfile", "conf/worldlist.conf").c_str());
|
||||||
|
marksLUT = new ACFile(mainConf->getValue("lutfile", "conf/lut.conf").c_str());
|
||||||
replylist = new ACFile(mainConf->getValue("replyfile", "conf/replies.conf").c_str());
|
replylist = new ACFile(mainConf->getValue("replyfile", "conf/replies.conf").c_str());
|
||||||
messages = new AMFile(mainConf->getValue("messages", "conf/messages.list").c_str());
|
messages = new AMFile(mainConf->getValue("messages", "conf/messages.list").c_str());
|
||||||
filth = new ALFile(mainConf->getValue("filthlist", "conf/filth.list").c_str());
|
filth = new ALFile(mainConf->getValue("filthlist", "conf/filth.list").c_str());
|
||||||
|
@ -99,6 +113,23 @@ void loadConfig() {
|
||||||
spin = mainConf->getInt("spin", 0);
|
spin = mainConf->getInt("spin", 0);
|
||||||
keepAliveTime = mainConf->getInt("katime", 15);
|
keepAliveTime = mainConf->getInt("katime", 15);
|
||||||
debug = mainConf->getInt("debug", 0) == 1;
|
debug = mainConf->getInt("debug", 0) == 1;
|
||||||
|
|
||||||
|
// parse json routine
|
||||||
|
//
|
||||||
|
// data structure per entry is as follows:
|
||||||
|
// name (friendly name used during tours)
|
||||||
|
// url (the file location)
|
||||||
|
// room (the chatroom ID for the roomserver)
|
||||||
|
// position (an array containing the bot's resting position for that world while on tour)
|
||||||
|
// position is an array with a length of 4, containing X, Y, Z, and yaw in that order.
|
||||||
|
// blacklist (bool which skips the world during diceroll tours)
|
||||||
|
// useful for preventing GZ reskins from appearing, exceptions made where appropriate
|
||||||
|
// blacklisted worlds still need a specified idle position!
|
||||||
|
//
|
||||||
|
// feature idea: commentary/notes keyvalue pair which the bot will speak when entering a certain world, used to give tips ??
|
||||||
|
|
||||||
|
std::ifstream dictionaryStream("conf/marks.json"); // look for worldsmarks database in the configuration folder and open it
|
||||||
|
worldsmarks = json::parse(dictionaryStream); // parse and remember data globally
|
||||||
}
|
}
|
||||||
|
|
||||||
int deinit(int response) {
|
int deinit(int response) {
|
||||||
|
@ -157,23 +188,11 @@ void roomInit() {
|
||||||
rAutoMsg_t = std::thread(autoRandMessage);
|
rAutoMsg_t = std::thread(autoRandMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void roomKeepAlive() {
|
|
||||||
sleep(1);
|
|
||||||
teleport(&roomsock, xPos, yPos, zPos, direction);
|
|
||||||
while (roomOnline) {
|
|
||||||
if (direction >= 360) direction += (spin - 360);
|
|
||||||
else direction+=spin;
|
|
||||||
longloc(&roomsock, xPos, yPos, zPos, direction);
|
|
||||||
sleep(keepAliveTime);
|
|
||||||
}
|
|
||||||
printf("warning: room keep alive disconnected!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void autoRandMessage() {
|
void autoRandMessage() {
|
||||||
int minTime = mainConf->getInt("minRandomMsgTime", 0);
|
int minTime = mainConf->getInt("minRandomMsgTime", 0);
|
||||||
int maxTime = mainConf->getInt("maxRandomMsgTime", 0);
|
int maxTime = mainConf->getInt("maxRandomMsgTime", 0);
|
||||||
int wait = 0;
|
int wait = 600;
|
||||||
sendChatMessage(&roomsock, messages->getMessage("startup"));
|
//sendChatMessage(&roomsock, messages->getMessage("startup"));
|
||||||
if (minTime != 0) {
|
if (minTime != 0) {
|
||||||
while (roomOnline) {
|
while (roomOnline) {
|
||||||
if (wait != 0) {
|
if (wait != 0) {
|
||||||
|
@ -349,11 +368,15 @@ void reciever(int *sock, uint16_t port, bool* status) {
|
||||||
*status = false;
|
*status = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this only works correctly if using POSIX-compliant line endings in the bot.conf file
|
||||||
|
// CR+LF breaks everything, should probably fix this if this becomes widely used at any point!
|
||||||
void sessInit(int *sock, std::string username, std::string password) {
|
void sessInit(int *sock, std::string username, std::string password) {
|
||||||
bufout[1] = 0x01;
|
bufout[1] = 0x01;
|
||||||
bufout[2] = CMD_SESSINIT;
|
bufout[2] = CMD_SESSINIT;
|
||||||
int l = 3;
|
int l = 3; // packet construction buffer
|
||||||
|
|
||||||
|
std::cout << "Logging in with screenname " + username + "\n"; //debug
|
||||||
|
|
||||||
// Username
|
// Username
|
||||||
bufout[l++] = 2;
|
bufout[l++] = 2;
|
||||||
bufout[l++] = username.length();
|
bufout[l++] = username.length();
|
||||||
|
@ -395,6 +418,7 @@ void sessInit(int *sock, std::string username, std::string password) {
|
||||||
|
|
||||||
bufout[0] = l;
|
bufout[0] = l;
|
||||||
bufout[l+1] = 0;
|
bufout[l+1] = 0;
|
||||||
|
|
||||||
qsend(sock, bufout, false);
|
qsend(sock, bufout, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,6 +779,82 @@ bool handleGroups(char* buffer, std::string from, std::string message) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// teleport using a markEntry loaded from the database
|
||||||
|
void handleTeleportRequest(internalTypes::markEntry details) {
|
||||||
|
std::cout << "info: delaying teleport by 1000ms to avoid race condition\n";
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
std::cout << "info: requesting to join room \"" + details.room + "\"\n";
|
||||||
|
//roomIDReq(&roomsock, details.room); //fatal error
|
||||||
|
//room = details.room;
|
||||||
|
|
||||||
|
std::cout << "info: setting position at " +
|
||||||
|
std::to_string(details.position.x) + ", " +
|
||||||
|
std::to_string(details.position.y) + ", " +
|
||||||
|
std::to_string(details.position.z) + ", " +
|
||||||
|
std::to_string(details.position.yaw) + "\n";
|
||||||
|
xPos = details.position.x; // remember so that the idle thread doesn't rubberband us
|
||||||
|
yPos = details.position.y;
|
||||||
|
zPos = details.position.z;
|
||||||
|
direction = details.position.yaw;
|
||||||
|
teleport(&roomsock, xPos, yPos, zPos, direction); // force positional update immediately
|
||||||
|
|
||||||
|
std::cout << "info: updating goto destination\n";
|
||||||
|
realLocation = details.url;
|
||||||
|
|
||||||
|
std::cout << "info: done! initiating watchdog\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// unimplemented
|
||||||
|
void handleTour(std::string destination[4]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks performed each heartbeat for the duration of a tour
|
||||||
|
void tourCaretaker() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid redundant CTRL + V code in handlePhrase()
|
||||||
|
void lookUpWorldName(std::string alias, char* buffer/*replies*/) {
|
||||||
|
auto key = worldsmarks.find(alias);
|
||||||
|
if (key != worldsmarks.end()) {
|
||||||
|
std::cout << "info: found world \"" + alias + "\" in database, commencing teleport.\n";
|
||||||
|
|
||||||
|
internalTypes::markEntry details;
|
||||||
|
json failsafe = { // leftovers
|
||||||
|
{"name", "FALLBACK"},
|
||||||
|
{"url", "rel:GroundZero/GroundZero.world"},
|
||||||
|
{"room", "GroundZero#Reception"},
|
||||||
|
{"position", {0, 0, 0, 0}},
|
||||||
|
{"blacklist", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeid((*key)["position"][0]).name() != "int" ||
|
||||||
|
typeid((*key)["position"][1]).name() != "int" ||
|
||||||
|
typeid((*key)["position"][2]).name() != "int" ||
|
||||||
|
typeid((*key)["position"][3]).name() != "int") {
|
||||||
|
//std::cout << "ERROR: expected type int when reading position. PLEASE CHECK your marks.json!!!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
details.name = (*key)["name"].get<std::string>();
|
||||||
|
details.url = (*key)["url"].get<std::string>();
|
||||||
|
details.room = (*key)["room"].get<std::string>();
|
||||||
|
details.position.x = (*key)["position"][0];
|
||||||
|
details.position.y = (*key)["position"][1];
|
||||||
|
details.position.z = (*key)["position"][2];
|
||||||
|
details.position.yaw = (*key)["position"][3];
|
||||||
|
|
||||||
|
sprintf(buffer, mainConf->getValue("world_found_msg", "Taking you there now, teleport to me!").c_str());
|
||||||
|
handleTeleportRequest(details);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "ERROR: no world with the name \"" + alias + "\" exists! aborting teleport request\n";
|
||||||
|
sprintf(buffer, mainConf->getValue("world_not_found_msg", "Sorry, I don't know that one.").c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool handlePhrase(char* buffer, std::string from, std::string message) {
|
bool handlePhrase(char* buffer, std::string from, std::string message) {
|
||||||
std::vector<std::string> args = split(message, ' ');
|
std::vector<std::string> args = split(message, ' ');
|
||||||
if (strcontains("flip a coin", message)) {
|
if (strcontains("flip a coin", message)) {
|
||||||
|
@ -780,6 +880,35 @@ bool handlePhrase(char* buffer, std::string from, std::string message) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in the interest of making things easier programmatically
|
||||||
|
// i want to generate LUTs to map phrases to the JSON
|
||||||
|
// which will allow aliases for each entry in the db
|
||||||
|
//
|
||||||
|
// having an admin command like "makelut" to instantly generate these
|
||||||
|
// by recursively scanning the marks file would be nice
|
||||||
|
else if (strcontains("take me to", message)) {
|
||||||
|
std::cout << "info: remote user requested teleportation!\n";
|
||||||
|
// lookup LUT first for aliases and then check JSON for key
|
||||||
|
// using worldsmarks.find(message) if no alias present
|
||||||
|
// spit back error if both are blank
|
||||||
|
|
||||||
|
// truncate message to get just the name
|
||||||
|
std::string truncName = message.erase(message.find("take me to "), 11);
|
||||||
|
|
||||||
|
// then perform search
|
||||||
|
std::string alias = getValueOfIncludedName(marksLUT->get(), truncName);
|
||||||
|
if (alias.length() > 0) {
|
||||||
|
std::cout << "info: found world with alias \"" + truncName + "\" in lookup table.\n";
|
||||||
|
lookUpWorldName(alias, buffer);
|
||||||
|
}
|
||||||
|
else { // we didn't find an alias, search the DB directly
|
||||||
|
std::cout << "info: no world with the alias \"" + truncName + "\" is in the lookup table.\n";
|
||||||
|
lookUpWorldName(truncName, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,6 +940,7 @@ void processText(int *sock, std::string username, std::string message) {
|
||||||
} else if (lowermsg == "ping") {
|
} else if (lowermsg == "ping") {
|
||||||
sprintf(msgout, mainConf->getValue("pong_msg", "Pong!").c_str());
|
sprintf(msgout, mainConf->getValue("pong_msg", "Pong!").c_str());
|
||||||
sendChatMessage(sock, std::string(msgout));
|
sendChatMessage(sock, std::string(msgout));
|
||||||
|
} else if (lowermsg.find("http") != std::string::npos) {
|
||||||
} else if (lowermsg.find("http") != std::string::npos) {
|
} else if (lowermsg.find("http") != std::string::npos) {
|
||||||
int pos;
|
int pos;
|
||||||
if ((pos = lowermsg.find("http://")) != std::string::npos || (pos = lowermsg.find("https://")) != std::string::npos) {
|
if ((pos = lowermsg.find("http://")) != std::string::npos || (pos = lowermsg.find("https://")) != std::string::npos) {
|
||||||
|
@ -866,6 +996,24 @@ void processWhisper(int *sock, std::string username, std::string message) {
|
||||||
}
|
}
|
||||||
sendWhisperMessage(sock, username, msgout);
|
sendWhisperMessage(sock, username, msgout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needs to process geolocation requests and respond with a file path for valid teleporting
|
||||||
|
if (lowermsg == "&|+where?") {
|
||||||
|
// intentionally omit bot location if we're touring so players always start at world start
|
||||||
|
// the bot should ideally be placed in the same starting room, within the player's view as soon as they spawn
|
||||||
|
//
|
||||||
|
// doing it this way requires that we handle bookmark room IDs separately from the destination URL
|
||||||
|
if (onTour == true) {
|
||||||
|
std::cout << "info: touring, sending truncated location\n";
|
||||||
|
sendWhisperMessage(sock, username, "&|+where>" + realLocation + "#" + room);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// are all these concats really fucking neccessary?
|
||||||
|
std::cout << "info: not touring, sending full location\n";
|
||||||
|
sendWhisperMessage(sock, username, "&|+where>" + realLocation + "#" + room
|
||||||
|
+ "@" + std::to_string(xPos) + "," + std::to_string(yPos) + "," + std::to_string(zPos));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendChatMessage(int *sock, std::string msg) {
|
void sendChatMessage(int *sock, std::string msg) {
|
||||||
|
@ -950,3 +1098,22 @@ void qsend(int *sock, unsigned char str[], bool queue) {
|
||||||
qp->flush(0);
|
qp->flush(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// heartbeat needed to prevent disconnects
|
||||||
|
// also used to track time
|
||||||
|
void roomKeepAlive() {
|
||||||
|
sleep(1);
|
||||||
|
teleport(&roomsock, xPos, yPos, zPos, direction);
|
||||||
|
while (roomOnline) {
|
||||||
|
// force update position to prevent a disconnect
|
||||||
|
if (direction >= 360) direction += (spin - 360);
|
||||||
|
else direction += spin;
|
||||||
|
longloc(&roomsock, xPos, yPos, zPos, direction);
|
||||||
|
|
||||||
|
// do routine checks and call scheduled events
|
||||||
|
if (onTour == true) {tourCaretaker();}
|
||||||
|
|
||||||
|
// wait until next heartbeat
|
||||||
|
sleep(keepAliveTime);
|
||||||
|
}
|
||||||
|
printf("warning: room keep alive disconnected!\n");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user