Beispiel #1
0
        public override bool replyReceived(ExpectedReply e, message m, bool messageFailed = false)
        {
            bool processed = false;
            chat c = Roboto.Settings.getChat(e.chatID);
            mod_xyzzy_chatdata chatData = c.getPluginData<mod_xyzzy_chatdata>();

            //did one of our outbound messages fail?
            if (messageFailed)
            {
                //TODO - better handling of failed outbound messages. Timeout player or something depending on status?
                try
                {
                    string message = "Failed Incoming expected reply";
                    if (c != null) { message += " for chat " + c.ToString(); }
                    if (m != null) { message += " recieved from chatID " + m.chatID + " from userID " + m.userID + " in reply to " + e.outboundMessageID; }

                log(message, logging.loglevel.high);
                }
                catch (Exception ex)
                {
                    log("Error thrown during failed reply processing " + ex.ToString(), logging.loglevel.critical);
                }
                return true;
            }

            else
            {
                log("Incoming expected reply for chat " + c.ToString() + " recieved from chatID " + m.chatID + " from userID " + m.userID + " in reply to " + e.outboundMessageID, logging.loglevel.verbose);
            }

            //Set up the game, once we get a reply from the user.
            if (e.messageData == "Settings")
            {
                if (m.text_msg == "Cancel") { } //do nothing, should just end and go back
                else if (m.text_msg == "Change Packs") { chatData.sendPackFilterMessage(m, 1); }
                else if (m.text_msg == "Re-deal") { chatData.reDeal(); }
                else if (m.text_msg == "Extend") { chatData.extend(); }
                else if (m.text_msg == "Reset") { chatData.reset(); }
                else if (m.text_msg == "Force Question") { chatData.forceQuestion(); }
                else if (m.text_msg == "Timeout") { chatData.askMaxTimeout(m.userID); }
                else if (m.text_msg == "Delay") { chatData.askMinTimeout(m.userID); }
                else if (m.text_msg == "Kick") { chatData.askKickMessage(m); }
                else if (m.text_msg == "Abandon")
                {
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, "Are you sure you want to abandon the game?", true, typeof(mod_xyzzy), "Abandon", -1, true, TelegramAPI.createKeyboard(new List<string>() { "Yes", "No" },2));
                }
                return true;
            }

            else if (e.messageData == "useDefaults")
            {
                if (m.text_msg == "Use Defaults")
                {
                    //add all the q's and a's based on the previous settings / defaults if a new game.
                    chatData.addQuestions();
                    chatData.addAllAnswers();
                    string keyboard = TelegramAPI.createKeyboard(new List<string> { "Start", "Cancel" }, 2);
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, "To start the game once enough players have joined click the \"Start\" button below. You will need three or more players to start the game.", true, typeof(mod_xyzzy), "Invites", -1, true, keyboard);
                    chatData.setStatus(xyzzy_Statuses.Invites);
                }
                else if (m.text_msg == "Configure Game" )
                {
                    TelegramAPI.GetExpectedReply(c.chatID, m.userID, "How many questions do you want the round to last for (-1 for infinite)", true, typeof(mod_xyzzy), "SetGameLength");
                    chatData.setStatus(xyzzy_Statuses.SetGameLength);
                }
                else if (m.text_msg == "Cancel")
                {

                    TelegramAPI.SendMessage(m.userID, "Cancelled setup");
                    chatData.setStatus(xyzzy_Statuses.Stopped);
                }
                else
                {
                    string kb = TelegramAPI.createKeyboard(new List<string>() { "Use Defaults", "Configure Game", "Cancel" }, 2);
                    long messageID = TelegramAPI.GetExpectedReply(c.chatID, m.userID, "Not a valid answer. Do you want to start the game with the default settings, or set advanced optons first? You can change these options later with /xyzzy_settings", true, typeof(mod_xyzzy), "useDefaults", 01,false,kb);
                }
                processed = true;
            }

            else if (e.messageData == "SetGameLength")
            {
                int questions;

                if (int.TryParse(m.text_msg, out questions) && questions >= -1)
                {
                    chatData.enteredQuestionCount = questions;
                    //next, ask which packs they want:
                    chatData.sendPackFilterMessage(m,1);
                    chatData.setStatus(xyzzy_Statuses.setPackFilter);
                }
                else
                {
                    TelegramAPI.GetExpectedReply(c.chatID, m.userID, m.text_msg + " is not a valid number. How many questions do you want the round to last for? -1 for infinite", true, typeof(mod_xyzzy), "SetGameLength");
                }
                processed = true;
            }

            //Set up the game filter, once we get a reply from the user.
            else if (e.messageData.StartsWith( "setPackFilter"))
            {
                //figure out what page we are on. Should be in the message data
                int currentPage = 1;
                bool success = int.TryParse(e.messageData.Substring(14), out currentPage);
                if (!success)
                {
                    currentPage = 1;
                    log("Expected messagedata to contain a page number. Was " + e.messageData, logging.loglevel.high);
                }
                //import a cardcast pack
                if (m.text_msg == "Import CardCast Pack")
                {
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, Helpers.cardCast.boilerPlate + "\n\r"
                        + "To import a pack, enter the pack code. To cancel, type 'Cancel'", true, typeof(mod_xyzzy), "cardCastImport");
                    if (chatData.status == xyzzy_Statuses.setPackFilter) { chatData.setStatus(xyzzy_Statuses.cardCastImport); }
                }
                else if (m.text_msg == "Next")
                {
                    currentPage++;
                    chatData.sendPackFilterMessage(m, currentPage);

                }
                else if (m.text_msg == "Prev")
                {
                    currentPage--;
                    chatData.sendPackFilterMessage(m, currentPage);
                }

                //enable/disable an existing pack
                else if (m.text_msg != "Continue")
                {
                    chatData.setPackFilter(m);
                    chatData.sendPackFilterMessage(m,currentPage);
                }
                //no packs selected, retry
                else if (chatData.packFilter.Count == 0)
                {
                    chatData.sendPackFilterMessage(m,1);
                }
                //This is presumably a continue now...
                else
                {
                    //are we adding this as part of the setup process?
                    if (chatData.status == xyzzy_Statuses.setPackFilter)
                    {
                        chatData.addQuestions();
                        chatData.addAllAnswers();

                        chatData.askMaxTimeout(m.userID);
                        chatData.setStatus(xyzzy_Statuses.setMaxHours);
                    }
                    else
                    {
                        //adding as part of a /settings. return to main
                        chatData.sendSettingsMessage(m);
                        //TelegramAPI.SendMessage(chatData.chatID, "Updated the pack list. New cards won't get added to the game until you restart, or /xyzzy_reDeal" );
                    }
                }
                processed = true;
            }

            //Cardcast importing
            else if (e.messageData == "cardCastImport")
            {
                if (m.text_msg == "Cancel")
                {
                    //return to plugins
                    chatData.sendPackFilterMessage(m,1);
                    if (chatData.status == xyzzy_Statuses.cardCastImport) { chatData.setStatus(xyzzy_Statuses.setPackFilter); }
                }
                else
                {
                    string importMessage;
                    Helpers.cardcast_pack pack = new Helpers.cardcast_pack();
                    bool success = importCardCastPack(m.text_msg, out pack, out importMessage);
                    if (success == true)
                    {
                        //reply to user
                        TelegramAPI.SendMessage(m.userID, importMessage);
                        //enable the filter
                        chatData.setPackFilter(m, pack.name);
                        //return to plugin selection
                        chatData.sendPackFilterMessage(m,1);
                        if (chatData.status == xyzzy_Statuses.cardCastImport) { chatData.setStatus(xyzzy_Statuses.setPackFilter); }
                    }
                    else
                    {
                        TelegramAPI.GetExpectedReply(chatData.chatID, m.userID,
                        "Couldn't add the pack. " + importMessage + ". To import a pack, enter the pack code. To cancel, type 'Cancel'", true, typeof(mod_xyzzy), "cardCastImport");
                    }
                }
                processed = true;
            }

            //work out the maxWaitTime (timeout)
            else if (e.messageData == "setMaxHours")
            {
                //try parse
                bool success = chatData.setMaxTimeout(m.text_msg);
                if (success && chatData.status == xyzzy_Statuses.setMaxHours ) //could be at another status if being set mid-game
                {
                    //move to the throttle
                    chatData.setStatus(xyzzy_Statuses.setMinHours);
                    chatData.askMinTimeout(m.userID);

                }
                else if (success)
                {
                    //success, called inflite
                    //TelegramAPI.SendMessage(e.chatID, "Set timeouts to " + (chatData.maxWaitTimeHours == 0 ? "No Timeout" : chatData.maxWaitTimeHours.ToString() + " hours") );
                    //adding as part of a /settings. return to main
                    chatData.sendSettingsMessage(m);

                }
                else {
                    //send message, and retry
                    TelegramAPI.SendMessage(m.userID, "Not a valid value!");
                    chatData.askMaxTimeout(m.userID);
                }
                processed = true;
            }

            //work out the minWaitTime (throttle)
            else if (e.messageData == "setMinHours")
            {
                //try parse
                bool success = chatData.setMinTimeout(m.text_msg);
                if (success && chatData.status == xyzzy_Statuses.setMinHours)//could be at another status if being set mid-game
                {

                    //Ready to start game - tell the player they can start when they want
                    string keyboard = TelegramAPI.createKeyboard(new List<string> { "Start", "Cancel" }, 2);
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, "To start the game once enough players have joined click the \"Start\" button below. You will need three or more players to start the game.", true, typeof(mod_xyzzy), "Invites", -1, true, keyboard);
                    chatData.setStatus(xyzzy_Statuses.Invites);

                }
                else if (success)
                {
                    //adding as part of a /settings. return to main
                    chatData.sendSettingsMessage(m);
                    //success, called inflite
                    //TelegramAPI.SendMessage(e.chatID, (chatData.minWaitTimeHours == 0 ? "Game throttling disabled" :  "Set throttle to only allow one round every " + chatData.minWaitTimeHours.ToString() + " hours"));
                }

                else
                {
                    //send message, and retry
                    TelegramAPI.SendMessage(m.userID, "Not a valid number!");
                    chatData.askMinTimeout(m.userID);
                }
                processed = true;
            }

            //start the game proper
            else if (chatData.status == xyzzy_Statuses.Invites && e.messageData == "Invites")
                // TBH, dont care what they reply with. Its probably "start" as thats whats on the keyboard, but lets not bother checking,
                //as otherwise we would have to do some daft bounds checking
                // && m.text_msg == "start")
            {
                if (m.text_msg == "Cancel")
                {
                    //allow player to cancel, otherwise the message just keeps coming back.
                    chatData.setStatus(xyzzy_Statuses.Stopped);
                }
                else if (m.text_msg == "Override" && chatData.players.Count > 1)
                {
                    chatData.askQuestion(true);
                }
                else if (m.text_msg == "Start" && chatData.players.Count > 2)
                {
                    chatData.askQuestion(true);
                }
                else if (m.text_msg == "Start")
                {
                    string keyboard = TelegramAPI.createKeyboard(new List<string> { "Start", "Cancel" }, 2);
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, "Not enough players yet. You need three or more players to start the game. To start the game once enough players have joined click the \"Start\" button below.", true, typeof(mod_xyzzy), "Invites", -1, true, keyboard);
                }
                else
                {
                    string keyboard = TelegramAPI.createKeyboard(new List<string> { "Start", "Cancel" }, 2);
                    TelegramAPI.GetExpectedReply(chatData.chatID, m.userID, "To start the game once enough players have joined click the \"Start\" button below. You will need three or more players to start the game.", true, typeof(mod_xyzzy), "Invites", -1, true, keyboard);
                }

                processed = true;
            }

            //A player answering the question
            else if (chatData.status == xyzzy_Statuses.Question && e.messageData == "Question")
            {
                bool answerAccepted = chatData.logAnswer(m.userID, m.text_msg);
                processed = true;
                /*if (answerAccepted) - covered in the logAnswer step
                {
                    //no longer expecting a reply from this player
                    if (chatData.allPlayersAnswered())
                    {
                        chatData.beginJudging();
                    }
                }
                */
            }

            //A judges response
            else if (chatData.status == xyzzy_Statuses.Judging && e.messageData == "Judging" && m != null)
            {
                bool success = chatData.judgesResponse(m.text_msg);

                processed = true;
            }

            //abandon game
            else if (e.messageData == "Abandon")
            {
                chatData.setStatus(xyzzy_Statuses.Stopped);
                Roboto.Settings.clearExpectedReplies(c.chatID, typeof(mod_xyzzy));
                TelegramAPI.SendMessage(c.chatID, "Game abandoned. type /xyzzy_start to start a new game");
                processed = true;
            }

            //kicking a player
            else if (e.messageData == "kick")
            {
                mod_xyzzy_player p = chatData.getPlayer(m.text_msg);
                if (p != null)
                {
                    chatData.removePlayer(p.playerID);
                }
                chatData.check();
                //now return to the last settings page
                chatData.sendSettingsMessage(m);

                processed = true;
            }

            return processed;
        }
Beispiel #2
0
        /// <summary>
        /// Import a cardcast pack into the xyzzy localdata
        /// </summary>
        /// <param name="packFilter"></param>
        /// <returns>String containing details of the pack and cards added. String will be empty if import failed.</returns>
        public bool importCardCastPack(string packCode, out Helpers.cardcast_pack pack, out string response)
        {
            response = "";
            pack = new Helpers.cardcast_pack();
            bool success = false;
            int nr_qs = 0;
            int nr_as = 0;
            int nr_rep = 0;
            List<Helpers.cardcast_question_card> import_questions = new List<Helpers.cardcast_question_card>();
            List<Helpers.cardcast_answer_card> import_answers = new List<Helpers.cardcast_answer_card>();
            List<mod_xyzzy_chatdata> brokenChats = new List<mod_xyzzy_chatdata>();

            //don't sync again within x days. Add a random duration.
            //This line does nothing, as we overwrite the pack.
            //pack.nextSync = DateTime.Now.Add(new TimeSpan(5, settings.getRandom(23), 0, 0));

            try
            {
                log("Attempting to sync/import " + packCode);
                //Call the cardcast API. We should get an array of cards back (but in the wrong format)
                success = Helpers.cardCast.getPackCards(ref packCode, out pack, ref import_questions, ref import_answers);

                if (!success)
                {
                    response = "Failed to import pack from cardcast. Check that the code is valid";
                }
                else
                {
                    //lets just check if the pack already exists?
                    log("Retrieved " + import_questions.Count() + " questions and " + import_answers.Count() + " answers from Cardcast");
                    string l_packname = pack.name;
                    List<cardcast_pack> matchingPacks = getPackFilterList().Where(x => x.name == l_packname).ToList();

                    if (matchingPacks.Count > 1)  // .Contains(pack.name))
                    {
                        log("Multiple packs found for " + l_packname + " - aborting!", logging.loglevel.critical);
                        response += "/n/r" + "Aborting sync!";

                    }
                    else if (matchingPacks.Count == 1)
                    {
                        cardcast_pack updatePack = matchingPacks[0];

                        //sync the pack.
                        response = "Pack " + pack.name + " (" + packCode + ") exists, syncing cards";
                        log("Pack " + pack.name + "(" + packCode + ") exists, syncing cards", logging.loglevel.normal);

                        //remove any cached questions that no longer exist. Add them to a list first to allow us to loop;
                        List<mod_xyzzy_card> remove_cards = new List<mod_xyzzy_card>();
                        //ignore any cards that already exist in the cache. Adde them to a list first to allow us to loop;
                        List<mod_xyzzy_card> exist_cards = new List<mod_xyzzy_card>();
                        foreach (mod_xyzzy_card q in questions.Where(x => x.category == l_packname))
                        {
                            //find existing cards which don't exist in our import pack
                            if ((import_questions.Where(y => Helpers.common.cleanseText(y.question) == Helpers.common.cleanseText(q.text))).Count() == 0)
                            {
                                remove_cards.Add(q);
                            }
                            //if they do already exist, remove them from the import list (because they exist!)
                            else
                            {
                                exist_cards.Add(q);
                            }
                        }
                        //now remove them from the localdata
                        foreach (mod_xyzzy_card q in remove_cards)
                        {
                            questions.Remove(q);
                            //remove any cached questions
                            foreach(chat c in Roboto.Settings.chatData)
                            {

                                mod_xyzzy_chatdata chatData = (mod_xyzzy_chatdata) c.getPluginData(typeof(mod_xyzzy_chatdata));
                                if (chatData != null)
                                {
                                    chatData.remainingQuestions.RemoveAll(x => x == q.uniqueID);
                                    //if we remove the current question, invalidate the chat. Will reask a question once the rest of the import is done.
                                    if (chatData.currentQuestion == q.uniqueID)
                                    {
                                        log("The current question " + chatData.currentQuestion + " for chat " + c.chatID + " has been removed!");
                                        if (!brokenChats.Contains(chatData)) { brokenChats.Add(chatData); }
                                    }
                                }
                            }
                        }
                        //or from the import list
                        foreach (mod_xyzzy_card q in exist_cards)
                        {
                            //update the local text if it was a match-
                            cardcast_question_card match = null;
                            try
                            {
                                List< cardcast_question_card> matchedCards = import_questions.Where(y => Helpers.common.cleanseText(y.question) == Helpers.common.cleanseText(q.text)).ToList();
                                if (matchedCards.Count > 0) { match = matchedCards[0]; }
                                else
                                {
                                    log("Local card couldnt be found. Tried to match " + q.text , logging.loglevel.normal);
                                }
                            }
                            catch (Exception e)
                            {
                                log("Error finding cleansed version of q card - " + e.Message, logging.loglevel.critical);
                            }

                            if (match != null && q.text != match.question)
                            {
                                try
                                {
                                    log("Question text updated from " + q.text + " to " + match.question);
                                    q.text = match.question;
                                    q.nrAnswers = match.nrAnswers;
                                    nr_rep++;
                                }
                                catch (Exception e)
                                {
                                    log("Error updating question text on qcard - " + e.Message, logging.loglevel.critical);
                                }
                            }
                            try
                            {
                                int removed = import_questions.RemoveAll(x => x.question == q.text); //swallow this.
                            }
                            catch (Exception e)
                            {
                                log("Error removing qcard from importlist - " + e.Message, logging.loglevel.critical);
                            }
                        }
                        //add the rest to the localData
                        foreach (Helpers.cardcast_question_card q in import_questions)
                        {
                            mod_xyzzy_card x_question = new mod_xyzzy_card(q.question, pack.name, q.nrAnswers);
                            questions.Add(x_question);
                        }
                        response += "\n\r" + "Qs: Removed " + remove_cards.Count() + " from local. Skipped " + exist_cards.Count() + " as already exist. Updated " + nr_rep + ". Added " + import_questions.Count() + " new / replacement cards";

                        //do the same for the answer cards
                        nr_rep = 0;
                        remove_cards.Clear();
                        exist_cards.Clear();
                        foreach (mod_xyzzy_card a in answers.Where(x => x.category == l_packname))
                        {
                            //find existing cards which don't exist in our import pack
                            if ((import_answers.Where(y => Helpers.common.cleanseText(y.answer) == Helpers.common.cleanseText(a.text))).Count() == 0)
                            {
                                remove_cards.Add(a);
                            }
                            //if they do already exist, remove them from the import list (because they exist!)
                            else
                            {
                                exist_cards.Add(a);
                            }
                        }
                        //now remove them from the localdata
                        foreach (mod_xyzzy_card a in remove_cards) { answers.Remove(a); }
                        //or from the import list
                        foreach (mod_xyzzy_card a in exist_cards)
                        {
                            //update the local text if it was a match-ish
                            List<cardcast_answer_card> amatches = import_answers.Where(y => Helpers.common.cleanseText(y.answer) == Helpers.common.cleanseText(a.text)).ToList();
                            if (amatches.Count > 0)
                            {
                                cardcast_answer_card matcha = amatches[0];
                                if (a.text != matcha.answer)
                                {
                                    log("Answer text updated from " + a.text + " to " + matcha.answer);
                                    a.text = matcha.answer;
                                    nr_rep++;
                                }
                            }
                            else
                            {
                                log("Couldnt find card to update! " + a.text, logging.loglevel.high);
                            }

                            int removed = import_answers.RemoveAll(x => x.answer == a.text);
                        }
                        //add the rest to the localData
                        foreach (Helpers.cardcast_answer_card a in import_answers)
                        {
                            mod_xyzzy_card x_answer = new mod_xyzzy_card(a.answer, pack.name);
                            answers.Add(x_answer);
                        }

                        response += "\n\r" + "As: Removed " + remove_cards.Count() + " from local. Skipped " + exist_cards.Count() + " as already exist. Updated " + nr_rep + ". Added " + import_answers.Count() + " new / replacement cards";

                        updatePack.description = pack.description;
                        //don't sync again within x days. Add a random duration.
                        updatePack.nextSync = DateTime.Now.Add(new TimeSpan(5, settings.getRandom(23), 0, 0));
                        response += "\n\r" + "Next sync " + updatePack.nextSync.ToString("f") + ".";
                        //pack.description = outpack.description;

                        Roboto.Settings.stats.logStat(new statItem("Packs Synced", typeof(mod_xyzzy)));

                        success = true;
                    }
                    else
                    {
                        response += "Importing fresh pack " + pack.packCode + " - " + pack.name + " - " + pack.description;
                        foreach (Helpers.cardcast_question_card q in import_questions)
                        {
                            mod_xyzzy_card x_question = new mod_xyzzy_card(q.question, pack.name, q.nrAnswers);
                            questions.Add(x_question);
                            nr_qs++;
                        }
                        foreach (Helpers.cardcast_answer_card a in import_answers)
                        {
                            mod_xyzzy_card x_answer = new mod_xyzzy_card(a.answer, pack.name);
                            answers.Add(x_answer);
                            nr_as++;
                        }

                        response += "\n\r" + "Next sync " + pack.nextSync.ToString("f") + ".";

                        response += "\n\r" + "Added " + nr_qs.ToString() + " questions and " + nr_as.ToString() + " answers.";
                        packs.Add(pack);
                        response += "\n\r" + "Added " + pack.name + " to filter list.";
                    }

                }
            }
            catch (Exception e)
            {
                log("Failed to import pack " + e.ToString(), logging.loglevel.critical);
                success = false;
            }

            foreach (mod_xyzzy_chatdata c in brokenChats)
            {
                c.askQuestion(false);
            }

            log(response, logging.loglevel.normal);

            return success;
        }
Beispiel #3
0
        /// <summary>
        /// Import / Sync a cardcast pack into the xyzzy localdata
        /// </summary>
        /// <param name="packCode"></param>
        /// <param name="pack"></param>
        /// <param name="response"> String containing details of the pack and cards added.String will be empty if import failed.</param>
        /// <returns>success/fil</returns>
        public bool importCardCastPack(string packCode, out Helpers.cardcast_pack pack, out string response)
        {
            response = "";
            pack     = new Helpers.cardcast_pack();
            bool success = false;
            int  nr_qs   = 0;
            int  nr_as   = 0;
            int  nr_rep  = 0;
            List <Helpers.cardcast_question_card> import_questions = new List <Helpers.cardcast_question_card>();
            List <Helpers.cardcast_answer_card>   import_answers   = new List <Helpers.cardcast_answer_card>();
            List <mod_xyzzy_chatdata>             brokenChats      = new List <mod_xyzzy_chatdata>();

            logging.longOp lo_sync = new logging.longOp("XYZZY - Packsync", 5);

            try
            {
                log("Attempting to sync/import " + packCode);
                //Call the cardcast API. We should get an array of cards back (but in the wrong format)
                //note that this directly updates the pack object we are going to return - so need to shuffle around later if we sync a pack
                success = Helpers.cardCast.getPackCards(ref packCode, out pack, ref import_questions, ref import_answers);

                lo_sync.addone();

                if (!success)
                {
                    response = "Failed to import pack from cardcast. Check that the code is valid";
                }
                else
                {
                    //lets just check if the pack already exists?
                    log("Retrieved " + import_questions.Count() + " questions and " + import_answers.Count() + " answers from Cardcast");
                    Guid l_packID = pack.packID;
                    List <cardcast_pack> matchingPacks = getPackFilterList().Where(x => x.packCode == packCode).ToList();



                    if (matchingPacks.Count > 1)  // .Contains(pack.name))
                    {
                        log("Multiple packs found for " + l_packID + " - aborting!", logging.loglevel.critical);
                        response += "/n/r" + "Aborting sync!";
                    }
                    else if (matchingPacks.Count == 1)
                    {
                        cardcast_pack updatePack = matchingPacks[0];

                        //sync the pack.
                        response = "Pack " + pack.name + " (" + packCode + ") exists, syncing cards";
                        log("Pack " + pack.name + "(" + packCode + ") exists, syncing cards", logging.loglevel.normal);

                        //===================
                        //QUESTIONS
                        //===================

                        //NB: Used to do a first pass to remove any matching cards from the import list. This WONT work as it will remove the ability
                        //to find out how many copies of a card we should have! Instead, take a backup copy, remove items from that, and then delete
                        //anything that is remaining at the end.
                        List <mod_xyzzy_card> questionCache = questions.Where(x => (x.packID == updatePack.packID)).ToList();
                        log("=============================", logging.loglevel.high);
                        log("Procesing " + questionCache.Count() + " QUESTION cards", logging.loglevel.high);
                        log("=============================", logging.loglevel.high);
                        logging.longOp lo_q = new logging.longOp("Questions", questionCache.Count());

                        //Loop through everything that is in the import list, removing items as we go.
                        while (import_questions.Count() > 0)
                        {
                            lo_q.updateLongOp(questionCache.Count()); //go backwards. This is just the remaining nr cards.
                            cardcast_question_card currentCard = import_questions[0];
                            //find how many other matches in the import list we have.
                            List <cardcast_question_card> matchingImportCards = import_questions.Where(x => Helpers.common.cleanseText(x.question) == Helpers.common.cleanseText(currentCard.question)).ToList();
                            if (matchingImportCards.Count() == 0)
                            {
                                log("Error! No matches found for card '" + currentCard.question + "' - expect at least 1", logging.loglevel.critical);
                            }
                            else
                            {
                                log("Processing " + matchingImportCards.Count() + " cards matching " + currentCard.question, logging.loglevel.verbose);
                                List <mod_xyzzy_card> matchingLocalCards = questions.Where(x => (x.packID == updatePack.packID) && (Helpers.common.cleanseText(x.text) == Helpers.common.cleanseText(currentCard.question))).ToList();
                                log("Found " + matchingLocalCards.Count() + " local cards", logging.loglevel.verbose);

                                //assume the first cards should match the cards coming from CardCast. Update so we have exact text
                                int j = 0;
                                while (j < matchingLocalCards.Count() && j < matchingImportCards.Count())
                                {
                                    if (matchingLocalCards[j].text != matchingImportCards[j].question || matchingLocalCards[j].nrAnswers != matchingImportCards[j].nrAnswers)
                                    {
                                        try
                                        {
                                            log("Local question card updated from " + matchingLocalCards[j].text + "(" + matchingLocalCards[j].nrAnswers + ") to " + matchingImportCards[j].question + " (" + matchingImportCards[j].nrAnswers + ")", logging.loglevel.high);
                                            matchingLocalCards[j].text      = matchingImportCards[j].question;
                                            matchingLocalCards[j].nrAnswers = matchingImportCards[j].nrAnswers;
                                            nr_rep++;
                                        }
                                        catch (Exception e)
                                        {
                                            log("Error updating question text on qcard - " + e.Message, logging.loglevel.critical);
                                        }
                                    }
                                    j++;
                                }

                                //for the remainder, decide how to progress:
                                if (matchingLocalCards.Count() == matchingImportCards.Count())
                                {
                                    //log("Count matches, nothing more to do!", logging.loglevel.verbose);
                                }
                                //Need to add some new cards
                                else if (matchingLocalCards.Count() < matchingImportCards.Count())
                                {
                                    log("Not enough local cards, adding " + (matchingLocalCards.Count() - matchingImportCards.Count()) + " extra", logging.loglevel.verbose);
                                    for (int i = matchingLocalCards.Count(); i < matchingImportCards.Count(); i++)
                                    {
                                        log("Adding card " + i + " " + matchingImportCards[0].question, logging.loglevel.verbose);
                                        mod_xyzzy_card x_question = new mod_xyzzy_card(matchingImportCards[0].question, pack.packID, matchingImportCards[0].nrAnswers);
                                        questions.Add(x_question);
                                    }
                                }
                                //need to remove some existing cards. Retire cards by merging data into the first card
                                else
                                {
                                    for (int i = matchingImportCards.Count(); i < matchingLocalCards.Count(); i++)
                                    {
                                        //merge the card from the master list, and flag any chats as broken
                                        List <mod_xyzzy_chatdata> newBrokenChats = removeQCard(matchingLocalCards[i], matchingLocalCards[0].uniqueID);
                                        if (newBrokenChats.Count() > 0)
                                        {
                                            log("Marking " + newBrokenChats.Count() + " chats as requiring checking", logging.loglevel.high);
                                        }
                                        brokenChats.AddRange(newBrokenChats);
                                    }
                                }
                            }
                            //now remove all the processed import cards from our import list, and our cache
                            foreach (cardcast_question_card c in matchingImportCards)
                            {
                                import_questions.Remove(c);
                                log("Removed card from import list: " + c.question, logging.loglevel.verbose);
                            }
                            int matches = questionCache.RemoveAll(x => Helpers.common.cleanseText(x.text) == Helpers.common.cleanseText(currentCard.question));
                            log("Removed " + matches + " from temporary local cache", logging.loglevel.verbose);
                        }

                        //now remove anything left in the cache from the master question list.
                        lo_sync.addone();
                        foreach (mod_xyzzy_card c in questionCache)
                        {
                            log("Card wasnt processed - disposing of " + c.text, logging.loglevel.warn);
                            //remove the card
                            List <mod_xyzzy_chatdata> addnBrokenChats = removeQCard(c, null);
                            brokenChats.AddRange(addnBrokenChats);
                        }
                        lo_q.complete();
                        lo_sync.addone();
                        //===================
                        //ANSWERS
                        //===================

                        //NB: Used to do a first pass to remove any matching cards from the import list. This WONT work as it will remove the ability
                        //to find out how many copies of a card we should have! Instead, take a backup copy, remove items from that, and then delete
                        //anything that is remaining at the end.

                        List <mod_xyzzy_card> answerCache = answers.Where(x => (x.packID == updatePack.packID)).ToList();
                        log("=============================", logging.loglevel.high);
                        log("Procesing " + answerCache.Count() + " ANSWER cards", logging.loglevel.high);
                        log("=============================", logging.loglevel.high);
                        logging.longOp lo_a = new logging.longOp("Answers", answerCache.Count());



                        //Loop through everything that is in the import list, removing items as we go.
                        while (import_answers.Count() > 0)
                        {
                            lo_a.updateLongOp(answerCache.Count());

                            cardcast_answer_card currentCard = import_answers[0];
                            //find how many other matches in the import list we have.
                            List <cardcast_answer_card> matchingImportCards = import_answers.Where(x => Helpers.common.cleanseText(x.answer) == Helpers.common.cleanseText(currentCard.answer)).ToList();
                            if (matchingImportCards.Count() == 0)
                            {
                                log("Error! No matches found for card '" + currentCard.answer + "' - expect at least 1", logging.loglevel.critical);
                            }
                            else
                            {
                                log("Processing " + matchingImportCards.Count() + " cards matching " + currentCard.answer, logging.loglevel.verbose);
                                List <mod_xyzzy_card> matchingLocalCards = answers.Where(x => (x.packID == updatePack.packID) && (Helpers.common.cleanseText(x.text) == Helpers.common.cleanseText(currentCard.answer))).ToList();
                                log("Found " + matchingLocalCards.Count() + " local cards", logging.loglevel.verbose);

                                //assume the first cards should match the cards coming from CardCast. Update so we have exact text
                                int j = 0;
                                while (j < matchingLocalCards.Count() && j < matchingImportCards.Count())
                                {
                                    if (matchingLocalCards[j].text != matchingImportCards[j].answer)
                                    {
                                        try
                                        {
                                            log("Local answer card updated from " + matchingLocalCards[j].text + " to " + matchingImportCards[j].answer, logging.loglevel.high);
                                            matchingLocalCards[j].text = matchingImportCards[j].answer;
                                            // matchingLocalCards[j].nrAnswers = matchingImportCards[j].nrAnswers; <- automatically set to -1 for an answer card.
                                            nr_rep++;
                                        }
                                        catch (Exception e)
                                        {
                                            log("Error updating answer text on acard - " + e.Message, logging.loglevel.critical);
                                        }
                                    }
                                    j++;
                                }

                                //for the remainder, decide how to progress:
                                if (matchingLocalCards.Count() == matchingImportCards.Count())
                                {
                                    //log("Count matches, nothing more to do!", logging.loglevel.verbose);
                                }
                                //Need to add some new cards (either doesnt exist locally - new card - or they have added another copy remotely)
                                else if (matchingLocalCards.Count() < matchingImportCards.Count())
                                {
                                    log("Not enough local cards, adding " + (matchingImportCards.Count() - matchingLocalCards.Count()) + " extra", logging.loglevel.verbose);
                                    for (int i = matchingLocalCards.Count(); i < matchingImportCards.Count(); i++)
                                    {
                                        log("Adding card " + i + " - " + matchingImportCards[0].answer, logging.loglevel.verbose);
                                        mod_xyzzy_card x_answer = new mod_xyzzy_card(matchingImportCards[0].answer, pack.packID, matchingImportCards[0].nrAnswers);
                                        answers.Add(x_answer);
                                    }
                                }
                                //need to remove some existing cards. Retire cards by merging data into the first card
                                else
                                {
                                    for (int i = matchingImportCards.Count(); i < matchingLocalCards.Count(); i++)
                                    {
                                        List <mod_xyzzy_chatdata> newBrokenChats = removeACard(matchingLocalCards[i], matchingLocalCards[0].uniqueID);
                                        if (newBrokenChats.Count() > 0)
                                        {
                                            log("Marking " + newBrokenChats.Count() + " chats as requiring checking", logging.loglevel.high);
                                        }
                                        brokenChats.AddRange(newBrokenChats);
                                    }
                                }
                            }
                            //now remove all the processed import cards from our import list, and our cache
                            lo_sync.addone();
                            foreach (cardcast_answer_card c in matchingImportCards)
                            {
                                import_answers.Remove(c);
                                log("Removed card from import list: " + c.answer, logging.loglevel.verbose);
                            }
                            int matches = answerCache.RemoveAll(x => Helpers.common.cleanseText(x.text) == Helpers.common.cleanseText(currentCard.answer));
                            log("Removed " + matches + " from temporary local cache", logging.loglevel.verbose);
                        }

                        //now remove anything left in the cache from the master answer list.
                        foreach (mod_xyzzy_card c in answerCache)
                        {
                            log("Card wasnt processed - suggests it was removed from the cardcast pack. Disposing of " + c.text, logging.loglevel.warn);
                            List <mod_xyzzy_chatdata> addnBrokenChats = removeQCard(c, null);
                            brokenChats.AddRange(addnBrokenChats);
                        }
                        lo_a.complete();
                        lo_sync.addone();


                        //Update the updatePack with the values from the imported pack
                        updatePack.description = pack.description;
                        updatePack.name        = pack.name;

                        //swap over our return objet to the one returned from CC.
                        pack = updatePack;

                        Roboto.Settings.stats.logStat(new statItem("Packs Synced", typeof(mod_xyzzy)));
                        lo_sync.addone();

                        success = true;
                    }
                    else
                    {
                        response += "Importing fresh pack " + pack.packCode + " - " + pack.name + " - " + pack.description;
                        logging.longOp lo_import = new logging.longOp("Import", import_answers.Count + import_questions.Count, lo_sync);
                        foreach (Helpers.cardcast_question_card q in import_questions)
                        {
                            mod_xyzzy_card x_question = new mod_xyzzy_card(q.question, pack.packID, q.nrAnswers);
                            questions.Add(x_question);
                            nr_qs++;
                            lo_import.addone();
                        }
                        foreach (Helpers.cardcast_answer_card a in import_answers)
                        {
                            mod_xyzzy_card x_answer = new mod_xyzzy_card(a.answer, pack.packID);
                            answers.Add(x_answer);
                            nr_as++;
                            lo_import.addone();
                        }

                        response += "\n\r" + "Next sync " + pack.nextSync.ToString("f") + ".";

                        response += "\n\r" + "Added " + nr_qs.ToString() + " questions and " + nr_as.ToString() + " answers.";
                        packs.Add(pack);
                        response += "\n\r" + "Added " + pack.name + " to filter list.";
                        lo_import.complete();
                    }
                }
            }
            catch (Exception e)
            {
                log("Failed to import pack " + e.ToString(), logging.loglevel.critical);
                success = false;
            }

            foreach (mod_xyzzy_chatdata c in brokenChats.Distinct())
            {
                c.check(true);
            }

            lo_sync.complete();
            log(response, logging.loglevel.normal);

            return(success);
        }