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; }
/// <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; }
/// <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); }