Пример #1
0
        /// <summary>
        /// Handle a failed outbound message that a plugin expects a reply for.
        /// </summary>
        /// <param name="er"></param>
        public void parseFailedReply(ExpectedReply er)
        {
            expectedReplies.Remove(er);
            Modules.RobotoModuleTemplate pluginToCall = null;

            foreach (Modules.RobotoModuleTemplate plugin in settings.plugins)
            {
                if (er.pluginType == plugin.GetType().ToString())
                {
                    //stash these for calling outside of the "foreach" loop. This is so we can be sure it is called ONCE only, and so that we can remove
                    //the expected reply before calling the method, so any post-processing works smoother.
                    pluginToCall = plugin;
                }
            }
            //now send it to the plugin (remove first, so any checks can be done)
            if (pluginToCall == null)
            {
                Roboto.log.log("Expected Reply wasnt on the stack - probably sent in immediate-mode! Couldnt remove it", logging.loglevel.normal);
            }
            else
            {
                bool pluginProcessed = pluginToCall.replyReceived(er, null, true);

                if (!pluginProcessed)
                {
                    Roboto.log.log("Plugin " + pluginToCall.GetType().ToString() + " didnt process the message it expected a reply to!", logging.loglevel.high);
                    throw new InvalidProgramException("Plugin didnt process the message it expected a reply to!");
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Add a new expected reply to the stack. Should be called internally only - New messages should be sent via TelegramAPI.GetExpectedReply
        /// </summary>
        /// <param name="e"></param>
        /// <param name="trySendImmediately">Try and send the message immediately, assuming nothing is outstanding. Will jump the queue, but not override any existing messages</param>
        /// <returns>An integer specifying the message id. -1 indicates it is queueed, long.MinValue indicates a failure</returns>
        public long newExpectedReply(ExpectedReply e, bool trySendImmediately)
        {
            //flag the user as present in the chat
            if (e.isPrivateMessage)
            {
                markPresence(e.userID, e.chatID, e.userName);
            }

            //check if we can send it? Get the messageID back
            long messageID = -1;

            //is this a message to a group?
            if (!e.isPrivateMessage)
            {
                //send, dont queue.
                //TODO - doesnt handle group PMs
                messageID = e.sendMessage();
            }
            //this is a PM. Does the user have anything in the queue?
            else if (!userHasOutstandingMessages(e.userID))
            {
                //send the message.
                messageID = e.sendMessage();
                //queue if it was a question
                if (e.expectsReply)
                {
                    expectedReplies.Add(e);
                }
            }
            //If they have messages in the queue, and we want to jump ahead, has one already been asked, or are we open?
            else if (trySendImmediately && !userHasOutstandingQuestions(e.userID))
            {
                Roboto.log.log("Message jumping queue due to immediatemode", logging.loglevel.verbose);
                //send the message, grab the ID.
                messageID = e.sendMessage();
                //did it work/
                if (messageID == long.MinValue)
                {
                    Roboto.log.log("Tried to send message using immediateMode, but it failed.", logging.loglevel.warn);
                    return(messageID);
                }

                //queue if it was a question
                else if (e.expectsReply)
                {
                    expectedReplies.Add(e);
                }
            }
            else
            {
                //chuck it on the stack if its going to be queued
                expectedReplies.Add(e);
            }

            //make sure we are in a safe state. This will make sure if we sent a message-only, that the next message(s) are processed.
            expectedReplyHousekeeping();

            return(messageID);
        }
Пример #3
0
        /// <summary>
        /// Send a message, which we are expecting a reply to. Message can be sent publically or privately. Replies will be detected and sent via the plugin replyReceived method.
        /// </summary>
        /// <param name="chatID"></param>
        /// <param name="text"></param>
        /// <param name="replyToMessageID"></param>
        /// <param name="selective"></param>
        /// <param name="answerKeyboard"></param>
        /// <returns>An integer specifying the message id. -1 indicates it is queueed, long.MinValue indicates a failure</returns>
        public static long GetExpectedReply(long chatID, long userID, string text, bool isPrivateMessage, Type pluginType, string messageData, string userName = null, long replyToMessageID = -1, bool selective = false, string answerKeyboard = "", bool useMarkdown = false, bool clearKeyboard = false, bool trySendImmediately = false)
        {
            ExpectedReply e = new ExpectedReply(chatID, userID, userName, text, isPrivateMessage, pluginType, messageData, replyToMessageID, selective, answerKeyboard, useMarkdown, clearKeyboard, true);

            //add the message to the stack. If it is sent, get the messageID back.
            long messageID = Roboto.Settings.newExpectedReply(e, trySendImmediately);

            return(messageID);
        }
Пример #4
0
        /// <summary>
        /// Send a message. Returns the ID of the send message
        /// </summary>
        /// <param name="chatID">User or Chat ID</param>
        /// <param name="text"></param>
        /// <param name="markDown"></param>
        /// <param name="replyToMessageID"></param>
        /// <returns>An integer specifying the message id. -1 indicates it is queued, int.MinValue indicates a failure</returns>
        public static long SendMessage(long chatID, string text, string userName = null, bool markDown = false, long replyToMessageID = -1, bool clearKeyboard = false, bool trySendImmediately = false)
        {
            bool          isPM = (chatID < 0 ? false : true);
            ExpectedReply e    = new ExpectedReply(chatID, chatID, userName, text, isPM, null, null, replyToMessageID, false, "", markDown, clearKeyboard, false);

            //add the message to the stack. If it is sent, get the messageID back.
            long messageID = Roboto.Settings.newExpectedReply(e, trySendImmediately);

            return(messageID);
        }
Пример #5
0
        /// <summary>
        /// Send a message. Returns the ID of the send message
        /// </summary>
        /// <param name="chatID">User or Chat ID</param>
        /// <param name="text"></param>
        /// <param name="markDown"></param>
        /// <param name="replyToMessageID"></param>
        /// <returns>An integer specifying the message id. -1 indicates it is queued, int.MinValue indicates a failure</returns>
        public static long SendMessage(long chatID, string text, bool markDown = false, long replyToMessageID = -1, bool clearKeyboard = false, bool trySendImmediately = false)
        {
            
            bool isPM = (chatID < 0 ? false : true);
            ExpectedReply e = new ExpectedReply(chatID, chatID, text, isPM , null, null, replyToMessageID, false, "", markDown, clearKeyboard, false);
            
            //add the message to the stack. If it is sent, get the messageID back.
            long messageID = Roboto.Settings.newExpectedReply(e, trySendImmediately);
            return messageID;

        }
Пример #6
0
 public void removeReply(ExpectedReply r)
 {
     expectedReplies.Remove(r);
 }
Пример #7
0
        public bool parseExpectedReplies(message m)
        {
            //are we expecteing this?
            bool processed = false;

            Modules.RobotoModuleTemplate pluginToCall = null;
            ExpectedReply er = null;

            try
            {
                foreach (ExpectedReply e in expectedReplies)
                {
                    //we are looking for direct messages from the user where c_id = m_id, OR reply messages where m_id = reply_id
                    //could trigger twice if we f****d something up - dont think this is an issue but checking processed flag for safety
                    if (!processed && e.isSent() && m.userID == e.userID)
                    {
                        if (m.chatID == e.userID || m.replyMessageID == e.outboundMessageID)
                        {
                            //find the plugin, send the expectedreply to it
                            foreach (Modules.RobotoModuleTemplate plugin in settings.plugins)
                            {
                                if (e.isOfType(plugin.GetType()))
                                {
                                    //stash these for calling outside of the "foreach" loop. This is so we can be sure it is called ONCE only, and so that we can remove
                                    //the expected reply before calling the method, so any post-processing works smoother.
                                    pluginToCall = plugin;
                                    er           = e;
                                }
                            }
                            processed = true;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Roboto.log.log("Error matching incoming message to plugin - " + e.ToString(), logging.loglevel.critical);
            }


            if (processed)
            {
                expectedReplies.Remove(er);
                //now send it to the plugin (remove first, so any checks can be done)
                try
                {
                    bool pluginProcessed = pluginToCall.replyReceived(er, m);

                    if (pluginProcessed)
                    {
                        //reset our chat timer
                        chat c = getChat(er.chatID);
                        c.resetLastUpdateTime();
                    }
                    else
                    {
                        throw new InvalidProgramException("Plugin didnt process the message it expected a reply to!");
                    }
                }
                catch (Exception e)
                {
                    Roboto.log.log("Error calling plugin " + pluginToCall.GetType().ToString() + " with expected reply. " + e.ToString(), logging.loglevel.critical);
                }

                /*/are there any more messages for the user? If so, find & send
                 * ExpectedReply messageToSend = null;
                 * foreach (ExpectedReply e in expectedReplies)
                 * {
                 *  if (e.userID == m.userID)
                 *  {
                 *      if (messageToSend == null || e.timeLogged < messageToSend.timeLogged)
                 *      {
                 *          messageToSend = e;
                 *      }
                 *
                 *  }
                 * }
                 *
                 * //send it
                 * //note that the plugin might send an urgent message during this processing that may have jumped the queue (using trySendImmediate param)
                 * if ( !userHasOutstandingMessages(m.userID) && messageToSend != null)
                 * {
                 *  messageToSend.sendMessage();
                 *  //make sure we are in a safe state. This will make sure if we sent a message-only, that the next message(s) are processed.
                 *  expectedReplyHousekeeping();
                 * }
                 */
                expectedReplyHousekeeping();
            }
            return(processed);
        }
Пример #8
0
        /// <summary>
        /// Make sure any reply processing is being done
        /// </summary>
        public void expectedReplyHousekeeping()
        {
            //Build up a list of user IDs
            //List<int> userIDs = new List<int>();
            //foreach (ExpectedReply e in expectedReplies) { userIDs.Add(e.userID); }
            //userIDs = (List<int>)userIDs.Distinct<int>();
            try
            {
                List <long> userIDs = expectedReplies.Select(e => e.userID).Distinct().ToList <long>();

                //remove any invalid messages
                List <ExpectedReply> messagesToRemove = expectedReplies.Where(e => e.outboundMessageID > 0 && e.expectsReply == false).ToList();
                if (messagesToRemove.Count > 0)
                {
                    Roboto.log.log("Removing " + messagesToRemove.Count() + " messages from queue as they are sent and dont require a reply", logging.loglevel.warn);
                }
                foreach (ExpectedReply e in messagesToRemove)
                {
                    expectedReplies.Remove(e);
                }



                foreach (long userID in userIDs)
                {
                    bool retry = true;
                    while (retry)
                    {
                        //for each user, check if a message has been sent, and track the oldest message
                        ExpectedReply        oldest      = null;
                        List <ExpectedReply> userReplies = expectedReplies.Where(e => e.userID == userID).ToList();

                        //try find a message to send. Drop out if we already have a sent message on the stack (waiting for a reply)
                        bool sent = false;
                        foreach (ExpectedReply e in userReplies)
                        {
                            if (e.isSent())
                            {
                                sent = true;
                            }                                //message is waiting
                            else
                            {
                                if (oldest == null || e.timeLogged < oldest.timeLogged)
                                {
                                    oldest = e;
                                }
                            }
                        }

                        //send the message if neccessary
                        if (!sent && oldest != null)
                        {
                            oldest.sendMessage();
                            if (!oldest.expectsReply)
                            {
                                expectedReplies.Remove(oldest);
                            }
                            //make sure we are in a safe state. This will make sure if we sent a message-only, that the next message(s) are processed.
                        }

                        //what do we do next?
                        if (sent == true)
                        {
                            retry = false;
                        }                                    // drop out if we have a message awaiting an answer
                        else if (oldest == null)
                        {
                            retry = false;
                        }                                           // drop out if we have no messages to send
                        else if (oldest.expectsReply)
                        {
                            retry = false;
                        }                                                //drop out if we sent a message that expects a reply
                    }
                }
            }
            catch (Exception e)
            {
                Roboto.log.log("Error during expected reply housekeeping " + e.ToString(), logging.loglevel.critical);
            }
        }
Пример #9
0
  /// <summary>
  /// Send a message, which we are expecting a reply to. Message can be sent publically or privately. Replies will be detected and sent via the plugin replyRecieved method. 
  /// </summary>
  /// <param name="chatID"></param>
  /// <param name="text"></param>
  /// <param name="replyToMessageID"></param>
  /// <param name="selective"></param>
  /// <param name="answerKeyboard"></param>
  /// <returns>An integer specifying the message id. -1 indicates it is queueed, long.MinValue indicates a failure</returns>
  public static long GetExpectedReply(long chatID, long userID, string text, bool isPrivateMessage, Type pluginType, string messageData, long replyToMessageID = -1, bool selective = false, string answerKeyboard = "", bool useMarkdown = false, bool clearKeyboard = false, bool trySendImmediately = false)
  {
      ExpectedReply e = new ExpectedReply(chatID, userID, text, isPrivateMessage, pluginType, messageData, replyToMessageID, selective, answerKeyboard, useMarkdown, clearKeyboard, true );
 
      //add the message to the stack. If it is sent, get the messageID back.
      long messageID = Roboto.Settings.newExpectedReply(e, trySendImmediately);
      return messageID;
  }
Пример #10
0
        /// <summary>
        /// Send the message in the expected reply. Should only be called from the expectedReply Class. May or may not expect a reply. 
        /// </summary>
        /// <param name="e"></param>
        /// <returns>A long specifying the message id. long.MinValue indicates a failure. Negative values are error codes</returns>
        public static long postExpectedReplyToPlayer(ExpectedReply e)
        {

            Roboto.Settings.stats.logStat(new statItem("Outgoing Msgs", typeof(TelegramAPI)));

            string postURL = Roboto.Settings.telegramAPIURL + Roboto.Settings.telegramAPIKey + "/sendMessage";

            //assemble collection of name/value data
            var pairs = new NameValueCollection();
            string chatID = e.isPrivateMessage ? e.userID.ToString() : e.chatID.ToString(); //send to chat or privately
            try
            {
                pairs.Add("chat_id", chatID);
                if (e.text.Length > 1950) { e.text = e.text.Substring(0, 1950); }


                //check if the user has participated in multiple chats recently, so we can stamp the message with the current chat title. 
                //only do this where the message relates to a chat. The chat ID shouldnt = the user id if this is the case. 
                if (e.isPrivateMessage && e.chatID != e.userID && e.chatID < 0)
                {
                    int nrChats = Roboto.Settings.getChatPresence(e.userID).Count();
                    if (nrChats > 1)
                    {
                        //get the current chat;
                        chat c = Roboto.Settings.getChat(e.chatID);
                        if (c == null)
                        {
                            Roboto.log.log("Couldnt find chat for " + e.chatID + " - did you use the userID accidentally?", logging.loglevel.high);
                        }
                        else
                        {
                            if (e.markDown && c.chatTitle != null) { e.text = "*" + c.chatTitle + "* :" + "\r\n" + e.text; }
                            else { e.text = "=>" + c.chatTitle + "\r\n" + e.text; }
                        }
                    }
                }
                pairs.Add("text", e.text);

                if (e.markDown) { pairs["parse_mode"] = "Markdown"; }

            }
            catch (Exception ex)
            {
                Roboto.log.log("Error assembling message!. " + ex.ToString(), logging.loglevel.critical);
            }
            try //TODO - cant see how this is erroring here. Added try/catch to try debug it.
            {
                //force a reply if we expect one, and the keyboard is empty
                if (e.expectsReply && (e.keyboard == null || e.keyboard == ""))

                {
                    bool forceReply = (!e.isPrivateMessage);

                    //pairs.Add("reply_markup", "{\"force_reply\":true,\"selective\":" + e.selective.ToString().ToLower() + "}");
                    pairs.Add("reply_markup", "{\"force_reply\":"
                        //force reply if we are NOT in a PM
                        + forceReply.ToString().ToLower()
                        //mark selective if passed in
                        + ",\"selective\":" + e.selective.ToString().ToLower() + "}");
                }

                else if (e.clearKeyboard) { pairs["reply_markup"] = "{\"hide_keyboard\":true}"; }
                else if (e.keyboard != null && e.keyboard != "")
                {
                    pairs.Add("reply_markup", "{" + e.keyboard + "}");
                }
                
            }
            catch (Exception ex)
            {
                //if we failed to attach, it probably wasnt important!
                Roboto.log.log("Error assembling message pairs. " + ex.ToString(), logging.loglevel.high);
            }
            try //TODO - cant see how this is erroring here. Added try/catch to try debug it.
            {
                if (e.replyToMessageID != -1)
                {
                    pairs.Add("reply_to_message_id", e.replyToMessageID.ToString());
                }
            }
            catch (Exception ex)
            {
                //if we failed to attach, it probably wasnt important!
                Roboto.log.log("Error attaching Reply Message ID to message. " + ex.ToString() , logging.loglevel.high); 
            }

            try
            {
                JObject response = sendPOST(postURL, pairs).Result;

                if (response != null)
                {

                    bool success = response.SelectToken("ok").Value<Boolean>();
                    if (success)
                    {
                        
                        JToken response_token = response.SelectToken("result");
                        if (response_token != null)
                        {
                            JToken messageID_token = response.SelectToken("result.message_id");
                            if (messageID_token != null)
                            {
                                int messageID = messageID_token.Value<int>();
                                return messageID;
                            }
                            else { Roboto.log.log("MessageID Token was null.", logging.loglevel.high); }
                        }

                        else { Roboto.log.log("Response Token was null.", logging.loglevel.high); }
                    }
                    else
                    {
                        int errorCode = response.SelectToken("error_code").Value<int>();
                        string errorDesc = response.SelectToken("description").Value<string>();

                        if (errorCode == 400 && errorDesc == "PEER_ID_INVALID")
                        {
                            //return a -403 for this - we want to signal that the call failed
                            Roboto.Settings.parseFailedReply(e);
                            return -403;
                        }
                        else if (errorCode == 403 && (errorDesc == "Bot was blocked by the user" || errorDesc == "Forbidden: Bot can't initiate conversation with a user"))
                        {
                            //return a -403 for this - we want to signal that the call failed
                            Roboto.Settings.parseFailedReply(e);
                            return -403;
                        }
                        else if (errorCode == 403 && errorDesc == "Forbidden: bot was kicked from the group chat")
                        {
                            //return a -403 for this - we want to signal that the call failed
                            Roboto.Settings.parseFailedReply(e);
                            return -403;
                        }



                        else
                        {
                            Roboto.log.log("Unmapped error recieved - " + errorCode + " " + errorDesc, logging.loglevel.high);
                            Roboto.Settings.parseFailedReply(e);
                            return -1;
                        }


                    }


                }
                else { Roboto.log.log("Response was null.", logging.loglevel.high); }

                Roboto.Settings.parseFailedReply(e);
                return long.MinValue;
            }
            catch (WebException ex)
            {
                Roboto.log.log("Couldnt send message to " + chatID.ToString() + " because " + ex.ToString(), logging.loglevel.high);

                //Mark as failed and return the failure to the calling method
                if (e.expectsReply)
                {
                    Roboto.log.log("Returning message " + e.messageData + " to plugin " + e.pluginType.ToString() + " as failed.", logging.loglevel.high);
                    Roboto.Settings.parseFailedReply(e);
                }
                return long.MinValue;
            }

            catch (Exception ex)
            {
                Roboto.log.log("Exception sending message to " + chatID.ToString() + " because " + ex.ToString(), logging.loglevel.high);

                //Mark as failed and return the failure to the calling method
                if (e.expectsReply)
                {
                    Roboto.log.log("Returning message " + e.messageData + " to plugin " + e.pluginType.ToString() + " as failed.", logging.loglevel.high);
                    Roboto.Settings.parseFailedReply(e);
                }
                return long.MinValue;

            }

        }
Пример #11
0
        /// <summary>
        /// Send the message in the expected reply. Should only be called from the expectedReply Class. May or may not expect a reply.
        /// </summary>
        /// <param name="e"></param>
        /// <returns>A long specifying the message id. long.MinValue indicates a failure. Negative values are error codes</returns>
        public static long postExpectedReplyToPlayer(ExpectedReply e)
        {
            Roboto.Settings.stats.logStat(new statItem("Outgoing Msgs", typeof(TelegramAPI)));

            string postURL = Roboto.Settings.telegramAPIURL + Roboto.Settings.telegramAPIKey + "/sendMessage";

            //assemble collection of name/value data
            var    pairs  = new NameValueCollection();
            string chatID = e.isPrivateMessage ? e.userID.ToString() : e.chatID.ToString(); //send to chat or privately

            try
            {
                pairs.Add("chat_id", chatID);
                if (e.text.Length > 1950)
                {
                    e.text = e.text.Substring(0, 1950);
                }


                //check if the user has participated in multiple chats recently, so we can stamp the message with the current chat title.
                //only do this where the message relates to a chat. The chat ID shouldnt = the user id if this is the case.
                if (e.isPrivateMessage && e.chatID != e.userID && e.chatID < 0)
                {
                    int nrChats = Roboto.Settings.getChatPresence(e.userID).Count();
                    if (nrChats > 1)
                    {
                        //get the current chat;
                        chat c = Roboto.Settings.getChat(e.chatID);
                        if (c == null)
                        {
                            Roboto.log.log("Couldnt find chat for " + e.chatID + " - did you use the userID accidentally?", logging.loglevel.high);
                        }
                        else
                        {
                            if (e.markDown && c.chatTitle != null)
                            {
                                e.text = "*" + c.chatTitle + "* :" + "\r\n" + e.text;
                            }
                            else
                            {
                                e.text = "=>" + c.chatTitle + "\r\n" + e.text;
                            }
                        }
                    }
                }
                pairs.Add("text", e.text);

                if (e.markDown)
                {
                    pairs["parse_mode"] = "Markdown";
                }
            }
            catch (Exception ex)
            {
                Roboto.log.log("Error assembling message!. " + ex.ToString(), logging.loglevel.critical);
            }
            try //TODO - cant see how this is erroring here. Added try/catch to try debug it.
            {
                //force a reply if we expect one, and the keyboard is empty
                if (e.expectsReply && (e.keyboard == null || e.keyboard == ""))

                {
                    bool forceReply = (!e.isPrivateMessage);

                    //pairs.Add("reply_markup", "{\"force_reply\":true,\"selective\":" + e.selective.ToString().ToLower() + "}");
                    pairs.Add("reply_markup", "{\"force_reply\":"
                              //force reply if we are NOT in a PM
                              + forceReply.ToString().ToLower()
                              //mark selective if passed in
                              + ",\"selective\":" + e.selective.ToString().ToLower() + "}");
                }

                else if (e.clearKeyboard)
                {
                    pairs["reply_markup"] = "{\"hide_keyboard\":true}";
                }
                else if (e.keyboard != null && e.keyboard != "")
                {
                    pairs.Add("reply_markup", "{" + e.keyboard + "}");
                }
            }
            catch (Exception ex)
            {
                //if we failed to attach, it probably wasnt important!
                Roboto.log.log("Error assembling message pairs. " + ex.ToString(), logging.loglevel.high);
            }
            try //TODO - cant see how this is erroring here. Added try/catch to try debug it.
            {
                if (e.replyToMessageID != -1)
                {
                    pairs.Add("reply_to_message_id", e.replyToMessageID.ToString());
                }
            }
            catch (Exception ex)
            {
                //if we failed to attach, it probably wasnt important!
                Roboto.log.log("Error attaching Reply Message ID to message. " + ex.ToString(), logging.loglevel.high);
            }

            try
            {
                JObject response = sendPOST(postURL, pairs).Result;

                if (response != null)
                {
                    bool success = response.SelectToken("ok").Value <Boolean>();
                    if (success)
                    {
                        JToken response_token = response.SelectToken("result");
                        if (response_token != null)
                        {
                            JToken messageID_token = response.SelectToken("result.message_id");
                            if (messageID_token != null)
                            {
                                int messageID = messageID_token.Value <int>();
                                return(messageID);
                            }
                            else
                            {
                                Roboto.log.log("MessageID Token was null.", logging.loglevel.high);
                            }
                        }

                        else
                        {
                            Roboto.log.log("Response Token was null.", logging.loglevel.high);
                        }
                    }
                    else
                    {
                        int    errorCode = response.SelectToken("error_code").Value <int>();
                        string errorDesc = response.SelectToken("description").Value <string>();

                        int result = parseErrorCode(errorCode, errorDesc);
                        Roboto.log.log("Message failed with code " + result, logging.loglevel.high);
                        Roboto.Settings.parseFailedReply(e);
                        return(result);
                    }
                }
                else
                {
                    Roboto.log.log("Response was null.", logging.loglevel.high);
                }

                Roboto.Settings.parseFailedReply(e);
                return(long.MinValue);
            }
            catch (WebException ex)
            {
                Roboto.log.log("Couldnt send message to " + chatID.ToString() + " because " + ex.ToString(), logging.loglevel.high);

                //Mark as failed and return the failure to the calling method
                if (e.expectsReply)
                {
                    Roboto.log.log("Returning message " + e.messageData + " to plugin " + e.pluginType.ToString() + " as failed.", logging.loglevel.high);
                    Roboto.Settings.parseFailedReply(e);
                }
                return(long.MinValue);
            }

            catch (Exception ex)
            {
                Roboto.log.log("Exception sending message to " + chatID.ToString() + " because " + ex.ToString(), logging.loglevel.high);

                //Mark as failed and return the failure to the calling method
                if (e.expectsReply)
                {
                    Roboto.log.log("Returning message " + e.messageData + " to plugin " + e.pluginType.ToString() + " as failed.", logging.loglevel.high);
                    Roboto.Settings.parseFailedReply(e);
                }
                return(long.MinValue);
            }
        }