Esempio n. 1
0
 /// <summary>
 /// Add data about a chat to the store.
 /// </summary>
 /// <param name="chat_id"></param>
 public chat addChat(long chat_id, string chatTitle)
 {
     if (getChat(chat_id) == null)
     {
         Console.WriteLine("Creating data for chat " + chat_id.ToString());
         chat chatObj = new chat(chat_id, chatTitle);
         chatData.Add(chatObj);
         return(chatObj);
     }
     else
     {
         throw new InvalidDataException("Chat already exists!");
     }
 }
Esempio n. 2
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);
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Do a healthcheck, and archive any old presence data
        /// Called from mod_standard's backgorund loop.
        /// </summary>
        public void expectedReplyBackgroundProcessing()
        {
            RecentChatMembers.RemoveAll(x => x.chatID == x.userID); //TODO <- this should be a startup housekeeping check only.

            //TODO - are we calling this whole thing every loop at the moment? Move to mod_standard.background?
            //Remove any stale presence info
            RecentChatMembers.RemoveAll(x => x.lastSeen < DateTime.Now.Subtract(new TimeSpan(chatPresenceExpiresAfterHours, 0, 0)));

            Roboto.log.log("There are " + expectedReplies.Count() + " expected replies on the stack", logging.loglevel.verbose);

            //main processing
            try
            {
                //Remove any ERs that are for dead chats
                List <ExpectedReply> deadERs = new List <ExpectedReply>();
                foreach (ExpectedReply er in expectedReplies)
                {
                    chat c = getChat(er.chatID);
                    if (c == null)
                    {
                        deadERs.Add(er);
                    }
                }
                foreach (ExpectedReply er in deadERs)
                {
                    expectedReplies.Remove(er);
                }
                Roboto.log.log("Removed " + deadERs.Count() + " dead expected replies, now " + expectedReplies.Count() + " remain", deadERs.Count() == 0 ? logging.loglevel.verbose : logging.loglevel.warn);

                //remove any expired ones
                int i = expectedReplies.RemoveAll(x => x.timeLogged < DateTime.Now.Subtract(TimeSpan.FromDays(Roboto.Settings.killInactiveChatsAfterXDays)));
                Roboto.log.log("Removed " + i + " expected replies, now " + expectedReplies.Count() + " remain", i == 0 ? logging.loglevel.verbose : logging.loglevel.warn);

                //Build up a list of user IDs
                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);
            }
        }
Esempio n. 4
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)
            {
                if (er == null)
                {
                    Roboto.log.log("Expected reply found, but er not available.", logging.loglevel.critical);
                    return(true);
                }
                if (pluginToCall == null)
                {
                    Roboto.log.log("Expected reply plugin found, but not available.", logging.loglevel.critical);
                    return(true);
                }

                //now send it to the plugin (remove first, so any checks can be done)
                expectedReplies.Remove(er);

                try
                {
                    bool pluginProcessed = pluginToCall.replyReceived(er, m);

                    if (pluginProcessed)
                    {
                        //reset our chat timer
                        chat c = getChat(er.chatID);
                        if (c != null)
                        {
                            c.resetLastUpdateTime();
                        }
                        else
                        {
                            Roboto.log.log("Chat not found for update.", logging.loglevel.critical);
                        }
                    }
                    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);
                }

                //Do any follow up er actions.
                expectedReplyBackgroundProcessing();
            }
            return(processed);
        }
Esempio n. 5
0
        private static void startBackground()
        {
            logging.longOp lo_s = new logging.longOp("Core Startup", 5);

            settings.loadPlugins();
            lo_s.addone();

            log.log("Loading Settings", logging.loglevel.high);
            Settings = settings.load();
            lo_s.addone();

            Settings.validate();
            lo_s.addone();

            log.initialise();
            lo_s.complete();


            log.log("I am " + Settings.botUserName, logging.loglevel.critical, Colors.White, false, true);
            Settings.startupChecks();


            //AT THIS POINT THE GAME WILL START PROCESSING INSTRUCTIONS!!!
            //DONT GO PAST IN STARTUP TEST MODE
            //----------------------------
            int ABANDONALLHOPE = 1;

            ABANDONALLHOPE++;
            //----------------------------

            Settings.save();

            if (Settings.isFirstTimeInitialised)
            {
                log.log(@"New XML file created in %appdata%\Roboto\ . Enter your API key in there and restart.", logging.loglevel.critical, Colors.White, false, true);
            }
            else
            {
                log.log("Starting main thread", logging.loglevel.high);

                DateTime lastUpdate = DateTime.MinValue;

                while (!endLoop)
                {
                    //store the time to prevent hammering the service when its down. Pause for a couple of seconds if things are getting toasty
                    if (lastUpdate > DateTime.Now.Subtract(TimeSpan.FromSeconds(10)))
                    {
                        Roboto.Settings.stats.logStat(new statItem("Hammering Prevention", typeof(Roboto)));
                        log.log("Too quick, sleeping", logging.loglevel.warn);
                        Thread.Sleep(2000);
                    }
                    lastUpdate = DateTime.Now;

                    //TODO - move this code to the webAPI class
                    string updateURL = Settings.telegramAPIURL + Settings.telegramAPIKey + "/getUpdates" +
                                       "?offset=" + Settings.getUpdateID() +
                                       "&timeout=" + Settings.waitDuration +
                                       "&limit=10";

                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(updateURL);
                    log.log(".", logging.loglevel.low, Colors.White, true);
                    request.Method      = "GET";
                    request.ContentType = "application/json";

                    try
                    {
                        WebResponse webResponse = request.GetResponse();
                        using (Stream webStream = webResponse.GetResponseStream())
                        {
                            if (webStream != null)
                            {
                                using (StreamReader responseReader = new StreamReader(webStream))
                                {
                                    string response = responseReader.ReadToEnd();

                                    JObject jo = JObject.Parse(response);

                                    //success?
                                    string path   = jo.First.Path;
                                    string result = jo.First.First.Value <string>();


                                    if (path != "ok" || result != "True")
                                    {
                                        endLoop = true;
                                        log.log("Failure code from web service", logging.loglevel.high);
                                        throw new WebException("Failure code from web service");
                                    }
                                    else
                                    {
                                        int resultID = 0;
                                        //open the response and parse it using JSON. Probably only one result, but should be more?
                                        foreach (JToken token in jo.SelectTokens("result.[*]"))//jo.Children()) //) records[*].data.importedPath"
                                        {
                                            string logText = Regex.Replace(token.ToString(), @"(\s|\n|\r|\r\n)+", " ");
                                            log.log(logText, logging.loglevel.verbose);

                                            //Find out what kind of message this is.

                                            //TOP LEVEL TOKENS
                                            JToken updateID_TK = token.First;
                                            JToken update_TK   = updateID_TK.Next.First;

                                            //Flag the update ID as processed.
                                            int updateID = updateID_TK.First.Value <int>();
                                            Settings.lastUpdate = updateID;

                                            //is this for a group chat?

                                            long chatID = update_TK.SelectToken("chat.id").Value <long>();

                                            chat chatData = null;
                                            if (chatID < 0)
                                            {
                                                //find the chat
                                                chatData = Settings.getChat(chatID);
                                                string chatTitle = update_TK.SelectToken("chat.title").Value <string>();
                                                //new chat, add
                                                if (chatData == null)
                                                {
                                                    chatData = Settings.addChat(chatID, chatTitle);
                                                }
                                                if (chatData == null)
                                                {
                                                    throw new DataMisalignedException("Something went wrong creating the new chat data");
                                                }
                                                chatData.setTitle(chatTitle);
                                            }



                                            //Do we have an incoming message?
                                            if (update_TK.Path == "result[" + resultID.ToString() + "].message" && update_TK.SelectToken(".text") != null)
                                            {
                                                //prevent delays - its sent something valid back to us so we are probably OK.
                                                lastUpdate = DateTime.MinValue;
                                                if (chatData != null)
                                                {
                                                    chatData.resetLastUpdateTime();
                                                }

                                                message m = new message(update_TK);

                                                //now decide what to do with this stuff.
                                                bool processed = false;

                                                //check if this is an expected reply, and if so route it to the
                                                Settings.parseExpectedReplies(m);


                                                //TODO - call plugins in some kind of priority order
                                                foreach (Modules.RobotoModuleTemplate plugin in settings.plugins)
                                                {
                                                    //Skip this message if the chat is muted.
                                                    if (plugin.chatHook && (chatData == null || (chatData.muted == false || plugin.chatIfMuted)))
                                                    {
                                                        if ((!processed || plugin.chatEvenIfAlreadyMatched))
                                                        {
                                                            processed = plugin.chatEvent(m, chatData);
                                                        }
                                                    }
                                                }
                                            }
                                            else
                                            {
                                                log.log("No text in update", logging.loglevel.verbose);
                                            }
                                            //dont know what other update types we want to monitor?
                                            //TODO - leave / kicked / chat deleted
                                            resultID++;
                                        }
                                        //NB: ER Housekepeping moved from here as called too frequently
                                    }
                                }
                            }
                        }
                    }
                    catch (System.Net.WebException e)
                    {
                        log.log("Web Service Timeout during getUpdates: " + e.ToString(), logging.loglevel.high);
                        Settings.stats.logStat(new statItem("BotAPI Timeouts", typeof(Roboto)));
                    }

                    catch (Exception e)
                    {
                        try
                        {
                            log.log("Exception caught at main loop. " + e.ToString(), logging.loglevel.critical, Colors.White, false, false, false, false, 2);
                        }

                        catch (Exception ex)
                        {
                            Console.Out.WriteLine("-----------------");
                            Console.Out.WriteLine("Error During LOGGING! Original Error was");
                            Console.Out.WriteLine(e.Message);
                            Console.Out.WriteLine("Logging Error was");
                            Console.Out.WriteLine(ex.Message);
                        }
                    }

                    //Perform all background processing, syncing etc..
                    Settings.backgroundProcessing(false);
                }

                log.log("Main loop finishing, saving", logging.loglevel.high);
                Roboto.Settings.save();
                log.log("Saved data, exiting main loop", logging.loglevel.high);
                //todo - do something to allow window to close?
                logWindow.unlockExit();
                log.log("All data saved cleanly - close the form again to exit", logging.loglevel.critical);
            }
        }