yourWaifu / sleepy-discord

C++ library for the Discord chat client. Please use Rust for new bots
https://yourWaifu.github.io/sleepy-discord/
MIT License
707 stars 95 forks source link

SleepyDiscord::DiscordClient::run() always exits when finished or disconnected #64

Open noglass opened 6 years ago

noglass commented 6 years ago

I'm unable to return back to the rest of my code once I've run the run() function.

Maybe it's just me, but I feel like my program should be able to try to recover in the event of an error instead of just crashing.. I tried wrapping the run() in a try block, but that solved nothing.

My code (it is a shared library loaded by dlfcn.h):

#define SLEEPY_ONE_THREAD // only use one thread because run() needs to be ran in its own thread

#include "sleepy_discord/websocketpp_websocket.h"
#include "halfmod.h"
#include <thread>
#include "discord_token.h" // defines DISCORD_TOKEN
#include <ctime>
#include <fstream>
#include "str_tok.h"

void internalQuit(SleepyDiscord::Message message, hmPlayer *player);

class myClientClass : public SleepyDiscord::DiscordClient {
public:
    using SleepyDiscord::DiscordClient::DiscordClient;
    void onMessage(SleepyDiscord::Message message)
    {
        bool ignore = false;
        for (auto it = ignoredChannels.begin(), ite = ignoredChannels.end();it != ite;++it)
        {
            if (*it == message.channelID)
            {
                ignore = true;
                break;
            }
        }
        if (!ignore)
        {
            bool prefixed = message.startsWith(cmdPrefix);
            std::string unprefixed = message.content;
            std::string cmd = "";
            std::string args = "";
            std::string id = std::string(message.author.ID);
            hmPlayer *player = nullptr;
            for (auto it = accounts.begin(), ite = accounts.end();it != ite;++it)
                if (it->userID == id)
                    player = &it->player;
            if (prefixed)
            {
                unprefixed.erase(0,cmdPrefix.size());
                cmd = unprefixed;
                size_t pos = unprefixed.find_first_of(" ");
                if (pos != std::string::npos)
                    cmd.erase(pos,std::string::npos);
                if (unprefixed.size() > cmd.size())
                {
                    args = unprefixed;
                    args.erase(0,cmd.size()+1);
                }
                if (cmd == "quit")
                    internalQuit(message,player);
                //else .... unimportant snip
            }
            for (auto it = triggers.begin(), ite = triggers.end();it != ite;++it)
                if (it->trigger & SLEEPY_MESSAGE)
                    if ((!it->prefix) || ((it->prefix) && (prefixed)))
                        if ((*it->func)(message.content,cmd,args,message.author.username,player,std::string(message.channelID),std::string(message.author.ID),(void*)&message))
                            break;
        }
    }
    void onError(SleepyDiscord::ErrorCode errorCode, const std::string errorMessage)
    {
        hmOutDebug(errorMessage);
    }
};

extern "C" {

myClientClass *discord;

void botConnect()
{
    loadAccounts();
    if (discord == nullptr)
    {
        std::thread([&]
        {
            try
            {
                discord->run();
            }
            catch (SleepyDiscord::ErrorCode err)
            {
                hmOutDebug("SleepyDiscord error code: " + to_string(int(err)));
            }
            hmOutDebug("Discord disconnected.");
        }).detach();
    }
}

void botDisconnect()
{
    //discord->stop(); // undefined?
    discord->quit();
}

int onExtensionLoad(hmExtension &handle, hmGlobal *global) // this function is called upon loading the module
{
    cmdPrefix = DEFAULT_PREFIX;
    discord = (myClientClass*)new myClientClass(DISCORD_TOKEN);
    recallGlobal(global);
    handle.extensionInfo("Discord Bot",
                         "nigel",
                         "Connect halfMod to Discord",
                         VERSION,
                         "http://discord.justca.me/in/minecraft");
    botConnect();
    handle.createTimer("msgBuffer",250,"sendBuffer","",MILLISECONDS);
    handle.hookPattern("message","^\\[[0-9]{2}:[0-9]{2}:[0-9]{2}\\] \\[Server thread/INFO\\]: <(\\S+?)> !discordlink (.+)$","confirmRecv");
    return 0;
}

}

void internalQuit(SleepyDiscord::Message message, hmPlayer *player)
{
    if ((player == nullptr) || ((player->flags & FLAG_CHAT) == 0))
        dcBuffer.push_back({message.channelID,"Sorry, " + message.author.username + ", you do not have access to this command."});
    else
    {
        discord->sendMessage(message.channelID,"I wish I could have been more useful :(");
        botDisconnect();
    }
}

Program output:

nigel@game01:~/current_halfmod$ ./halfmod_engine --debug localhost 9422
Extension "Discord Bot" loaded . . .
Plugin "Base Player Info" loaded . . .
Plugin "Reserved Slots" loaded . . .
Plugin "URLChat" loaded . . .
Plugin "Logs" loaded . . .
Plugin "Votes" loaded . . .
Plugin "Session Flags" loaded . . .
Plugin "Admin Commands" loaded . . .
[DEBUG] Error: No connection to the halfShell server . . .
Plugin "Mailbox" loaded . . .
[DEBUG] Error: Minimum Minecraft version for "disco" plugin is 1.13 official or snapshot 17w50a
Error loading plugin "./halfMod/plugins/disco.hmo" . . .
Plugin "MOTD" loaded . . .
Plugin "GeoIP" loaded . . .
Plugin "Discord Relay" loaded . . .
Plugin "Gamerule Cvars" loaded . . .
Link established with halfShell v0.3.3-build14
[2018-04-08 05:08:11] [connect] Successful connection
[05:08:11] [Server thread/INFO]: There are 0 of a max 20 players online: 
[2018-04-08 05:08:11] [connect] WebSocket Connection 104.16.59.37:443 v-2 "WebSocket++/0.7.0" /?v=6 101
[05:08:33] [Discord thread/MSG]: [nigel] Alrighty, I need to force halfBot to crashpooppoo sdafidkhow
[05:08:48] [Discord thread/MSG]: [nigel] oh
[05:08:57] [Discord thread/MSG]: [nigel] !quit
[2018-04-08 05:08:57] [warning] got non-close frame while closing
[2018-04-08 05:08:57] [error] handle_read_frame error: asio.ssl.stream:1 (stream truncated)
[2018-04-08 05:09:02] [disconnect] Disconnect close local:[1006,stream truncated] remote:[1000]
terminating with uncaught exception of type websocketpp::exception: Bad Connection
Aborted

Sorry for the extra code and output, but thought it might prove somewhat helpful.

TL;DR: When calling SleepyDiscord::DiscordClient::quit() the program will hang until what I assume is a heartbeat that notices it's no longer connected, throws an uncaught exception and exits without allowing the rest of the program to finish running its course. This also happens when any error occurs.

yourWaifu commented 6 years ago

Oh quit has been fixed in the devlop Branch. However, that has only been tested on Windows. However, I should be able to charrypick then into the master branch.

Also run blocks, do myClientClass(DISCORD_TOKEN, 3); this will make the client use 3 threads and it'll call run on a sperate thread for you.

yourWaifu commented 6 years ago

Actually, I just checked quit had already been fixed in the master branch. The issue had to do with websocketpp_websockets.cpp, I've already talked about a fix in issue #63

noglass commented 6 years ago

run blocking isn't the issue here. In fact, I kind of want it to, but when it's no longer running, it should stop blocking. Unless there is a way to return the current connection state?

Also, confirmed the master branch no longer crashes when disconnecting, but it doesn't ever return focus to the main thread even when run with 3 threads, it still blocks.

Wait.. I was doing something wrong. I need to run more tests first..

noglass commented 6 years ago

Okay the real issue is that the SleepyDiscord::DiscordClient constructor always calls run for you, even when in SLEEPY_ONE_THREAD mode. This should not happen, especially in a single threaded environment.

Ah ha! Just defining SLEEPY_ONE_THREAD isn't enough, I also had to set it to 1 on construction. Now everything works exactly as I would expect:

[2018-04-08 16:56:38] [connect] Successful connection
[2018-04-08 16:56:38] [connect] WebSocket Connection 104.16.60.37:443 v-2 "WebSocket++/0.7.0" /?v=6 101
[16:56:44] [Discord thread/MSG]: [nigel] !quit
[2018-04-08 16:56:44] [warning] got non-close frame while closing
[2018-04-08 16:56:44] [error] handle_read_frame error: asio.ssl.stream:1 (stream truncated)
[2018-04-08 16:56:49] [disconnect] Disconnect close local:[1006,stream truncated] remote:[1000]
[DEBUG] Discord disconnected.

As you can see, focus is returned to the main program when finished.

noglass commented 6 years ago

So now I have a new question. If run() disconnects and you call run() again, it doesn't do anything. Is there a way to make it reconnect?

yourWaifu commented 6 years ago

the only place that run is called in the library is here https://github.com/yourWaifu/sleepy-discord/blob/master/sleepy_discord/client.cpp#L27

I'm was doing test as of yesterday and I didn't run into those issues, but they do sound simalar to some issues I ran into a while back that were fixed with these few lines. https://github.com/yourWaifu/sleepy-discord/blob/master/sleepy_discord/websocketpp_websocket.cpp#L57 please check you have these.

as for your 2nd comment, umm I've never thought of doing that but there is an undocumented function that may work for that situation. reconnect in client.h. I'm not sure if it'll work here but worth a try I guess.

noglass commented 6 years ago

I was mistaken. I wasn't even calling run() myself. I had a silly check: if (discord == nullptr) before calling run(), but after constructing it so it wasn't even calling it, the constructor was calling it on its own because I wasn't specifically telling it to only use 1 thread. (I thought defining SLEEPY_ONE_THREAD was enough)

This issue can be closed now, everything works with the newest commit.

Thanks, I will try reconnect!