private void _OpenStory(WInteractiveStory story) { _story = story; // Update the Story info textbox // Update the Story Info textbox with the story's info StringBuilder sb = new StringBuilder(); sb.AppendLine($"Name: {story.Name}"); sb.AppendLine(); if (!string.IsNullOrWhiteSpace(story.ShortDescription)) { sb.AppendLine(story.ShortDescription); sb.AppendLine(); } sb.AppendLine("Description:"); sb.AppendLine(story.Description); UpdateStoryInfo(sb.ToString()); // Change the form tbStoryUrl.Text = story.Url; tbStoryUrl.ReadOnly = true; EnableSaveButton(true); EnableFetchStoryButton(true); log.InfoFormat("Story opened: {0}", story.UrlID); }
public static void SaveInteractiveStory(string filePath, WInteractiveStory storyToSave) { log.DebugFormat("Saving interactive story {0}", filePath); string serializedStory = SerializeInteractiveStoryToString(storyToSave); File.WriteAllText(filePath, serializedStory); }
public static string SerializeInteractiveStoryToString(WInteractiveStory storyToSerialize) { using (StringWriter sw = new StringWriter()) { storySerializer.Serialize(sw, storyToSerialize); return(sw.ToString()); } }
private void ResetForm() { log.Debug("Resetting form"); // Cancel an export, if one is running StoryExporter?.Cancel(); tbStoryUrl.ReadOnly = false; EnableCancelButton(false); EnableSaveButton(false); EnableFetchStoryButton(false); EnableOpenStoryButton(true); EnableCredentialFields(true); UpdateStatusProgress(0); UpdateStatusMessage("Open a story to get started"); tbStoryInfo.Clear(); _story = null; }
public void ExportStory(WInteractiveStory story) { UpdateStatusMessage("Exporting story to HTML"); try { var sfd = new SaveFileDialog(); sfd.Filter = STORY_EXPORT_DIALOG_FILTER; sfd.InitialDirectory = LastFileDir; sfd.FileName = $"{story.UrlID}.{STORY_EXPORT_DIALOG_SUFFIX}"; sfd.ValidateNames = true; sfd.OverwritePrompt = true; if (sfd.ShowDialog() == DialogResult.OK) { var htmlPayload = new WStoryToHtml().ConvertStory(story); File.WriteAllText(sfd.FileName, htmlPayload); if (MessageBox.Show( "The story has been exported.\n\nWould you like to see it?", "Story exported", MessageBoxButtons.YesNo, MessageBoxIcon.Information ) == DialogResult.Yes) { string args = $"/select,\"{sfd.FileName}\""; Process.Start("explorer", args); } LastFileDir = Path.GetDirectoryName(sfd.FileName); UpdateStatusMessage("Story exported to HTML"); log.Info("Story exported to HTML"); } } catch (Exception ex) { log.Error("Error while exporting story to HTML", ex); UpdateStatusMessage("Failed to export story to HTML"); } }
public async Task <WInteractiveStory> GetInteractive(Uri interactiveUrl) { WInteractiveStory newInteractive = new WInteractiveStory(); log.DebugFormat("Getting interactive story: {0}", interactiveUrl); string interactiveHtml = await HttpGetAsyncAsString(interactiveUrl); // DEBUG //string interactiveHtml = System.IO.File.ReadAllText("test-interactive-cover.html"); if (IsInteractivesUnavailablePage(interactiveHtml)) { throw new InteractivesTemporarilyUnavailableException(); } // Detect "Login required" or "access restricted" if (IsLoginPage(interactiveHtml)) { // We need to log in, and get the chapter again log.Debug("Login required while trying to get an interactive's details"); await LoginAsync(); // Get it again interactiveHtml = await HttpGetAsyncAsString(interactiveUrl); // Check if it's a login again. If it is, login failed if (IsLoginPage(interactiveHtml)) { throw new Exception("Failed to login to retrieve interactive details"); } } // Get the interactive story's title // This method grabs it from within the <title> element, not sure if it gets truncated or not. Regex interactiveTitleRegex = new Regex("(?<=<title>).+?(?= - Writing\\.Com<\\/title>)", RegexOptions.IgnoreCase); Match interactiveTitleMatch = interactiveTitleRegex.Match(interactiveHtml); if (!interactiveTitleMatch.Success) { throw new WritingClientHtmlParseException($"Couldn't find the title for interactive story '{interactiveUrl.ToString()}'", interactiveHtml); } string interactiveTitle = HttpUtility.HtmlDecode(interactiveTitleMatch.Value); // Get the interactive's tagline or short description // Previously this has been difficult o pin-point // However I found this on 11/01/2019, they've got it in a META tag at the top of the HTML // E.g. <META NAME="description" content="How will young James fare alone with his mature, womanly neighbors? "> Regex interactiveShortDescRegex = new Regex("(?<=<META NAME =\"description\" content=\").+?(?=\">)", RegexOptions.IgnoreCase); Match interactiveShortDescMatch = interactiveShortDescRegex.Match(interactiveHtml); if (!interactiveShortDescMatch.Success) { log.Warn($"Couldn't find the short description for interactive story '{interactiveUrl.ToString()}'"); // Just a warning, don't throw an exception over it } string interactiveShortDesc = HttpUtility.HtmlDecode(interactiveShortDescMatch.Value); // Get the interactive story's description Regex interactiveDescRegex = new Regex("(?<=<td align=left class=\"norm\">).+?(?=<\\/td>)", RegexOptions.IgnoreCase | RegexOptions.Singleline); Match interactiveDescMatch = interactiveDescRegex.Match(interactiveHtml); if (!interactiveDescMatch.Success) { throw new WritingClientHtmlParseException($"Couldn't find the description for interactive story '{interactiveUrl.ToString()}'", interactiveHtml); } string interactiveDesc = HttpUtility.HtmlDecode(interactiveDescMatch.Value); // TODO get author, looks like it'll be a pain in the ass telling the chapter author apart from the story author newInteractive.Url = interactiveUrl.ToString(); newInteractive.UrlID = interactiveUrl.Segments[interactiveUrl.Segments.Length - 1].TrimEnd('/'); // Did I just break this? newInteractive.Name = interactiveTitle; newInteractive.Description = interactiveDesc; newInteractive.ShortDescription = interactiveShortDesc; return(newInteractive); }
private void Init() { _cancelTokenSource = new CancellationTokenSource(); _story = null; IsExporting = false; }
public async Task <WInteractiveStory> ExportStory(WInteractiveStory story, WStoryExporterExportSettings exportSettings) { var cancelToken = _cancelTokenSource.Token; _story = story; if (IsExporting) { throw new Exception("Cannot export a story while already exporting a story"); } IsExporting = true; try { DoStateUpdate("Exporting story", 0, 1); // Get the story map await UpdateStory(); // Cancellation support if (cancelToken.IsCancellationRequested) { return(null); } // Update all the stories //foreach (var chapter in _story.Chapters) _progress = new WStoryExporterProgress(); _progress.ProgressMax = _story.Chapters.Count; for (var i = 0; i < _story.Chapters.Count; i++) { // Cancellation support if (cancelToken.IsCancellationRequested) { return(null); } _progress.ProgressIndex = i; var chapter = _story.Chapters[i]; if (string.IsNullOrEmpty(chapter.Content) || exportSettings.UpdateExisting == true) { DoStateUpdate($"Exporting chapter: {chapter.Path}", i, _story.Chapters.Count); await UpdateChapter(chapter); } } // Done _log.Info("Story export complete"); _progress = null; DoStateUpdate("Finished exporting story, ready to save", 1, 1); return(_story); } finally { IsExporting = false; _story = null; } }
public static string SerializeInteractiveStoryToString(WInteractiveStory storyToSerialize) { return(JsonConvert.SerializeObject(storyToSerialize)); }
public static void SerializeInteractiveStory(TextWriter stream, WInteractiveStory storyToSerialize) { JsonSerializer serializer = new JsonSerializer(); serializer.Serialize(stream, storyToSerialize); }
public static void SerializeInteractiveStory(Stream stream, WInteractiveStory storyToSerialize) { storySerializer.Serialize(stream, storyToSerialize); //return JsonConvert.SerializeObject(storyToSerialize); }
public string ConvertStory(WInteractiveStory story) { var output = string.Empty; // Make a copy of the chapter list, so we can do some work on it //var chapterList = new List<WInteractiveChapter>(story.Chapters); var chapterList = story.Chapters.OrderBy(c => c.Path).ToList(); // Do the story summary content block log.Debug("Building the story summary block"); var storySummaryContent = Templates["StorySummary"] .Replace("{StoryTitle}", story.Name) //.Replace("{StoryAuthor}", story.Author.Name) // Author isn't currently supported .Replace("{StoryShortDescription}", story.ShortDescription) // The tagline / short description isn't currentl supported .Replace("{StoryDescription}", story.Description); // Do the story chapter outline content block log.Debug("Building the story outline content block"); var chapterOutlineLines = new List <string>(); foreach (var chapter in chapterList) { chapterOutlineLines.Add( Templates["StoryOutlineItem"] .Replace("{ChapterPathDisplay}", StoryOutlineChapterPath(chapter.Path)) .Replace("{ChapterPath}", chapter.Path) .Replace("{ChapterName}", chapter.Title) ); } var chapterOutlineContent = Templates["StoryOutline"] .Replace("{Chapters}", string.Join("\n", chapterOutlineLines)); // Build the chapter content block var sbChapters = new StringBuilder(); foreach (var chapter in chapterList) { log.DebugFormat("Adding chapter {0}", chapter.Path); //DEBUG if (chapter.Path == "1131222") { var lolwut = "test"; } string choiceListBlock = ""; if (chapter.IsEnd) { choiceListBlock = Templates["ChapterChoiceListEnd"]; } else { var sbChapterChoices = new StringBuilder(); foreach (var choice in chapter.Choices) { bool isChoiceValid = story.Chapters.SingleOrDefault(c => c.Path == choice.MapLink) != null; // Choice links to a chapter that exists, choice is valid var choiceTemplate = isChoiceValid ? "ChapterChoiceValid" : "ChapterChoiceInvalid"; sbChapterChoices.AppendLine(Templates[choiceTemplate]) .Replace("{ChoicePath}", choice.MapLink) .Replace("{ChoiceName}", choice.Name); } choiceListBlock = Templates["ChapterChoiceList"] .Replace("{Choices}", sbChapterChoices.ToString()); } string sourceChoiceLink = String.Empty; if (chapter.Path != "1") { sourceChoiceLink = string.Format("<a href='#{0}'>Go back</a>", chapter.Path.Substring(0, chapter.Path.Length - 1)); } string chapterContentElementsClosed; string chapterContent = CloseUnclosedElements(chapter.Content, out chapterContentElementsClosed); if (!string.IsNullOrEmpty(chapterContentElementsClosed)) { log.Debug($"Chapter {chapter.Path} had unclosed HTML elements: {chapterContentElementsClosed}"); } sbChapters.AppendLine(Templates["Chapter"] .Replace("{Path}", chapter.Path) .Replace("{PathDisplay}", StoryOutlineChapterPath(chapter.Path)) .Replace("{Title}", chapter.Title) .Replace("{SourceChoice}", chapter.Path == "1" ? "(empty)" : chapter.SourceChoiceTitle) .Replace("{SourceChapterLink}", sourceChoiceLink) .Replace("{AuthorName}", chapter.Author.Name) .Replace("{Content}", chapterContent) .Replace("{Choices}", choiceListBlock) ); } // Final put together output = Templates["Page"] .Replace("{StorySummary}", storySummaryContent) .Replace("{StoryTitle}", story.Name) .Replace("{StoryOutline}", chapterOutlineContent) .Replace("{Chapters}", sbChapters.ToString()); return(output); }