using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net; using System.Text; using System.Threading.Tasks; using A69WD.Data; using A69WD.Service; using HtmlAgilityPack; using System.Globalization; using System.Xml.Serialization; namespace A69WD.Net { public class cLink_eRac : iLink { private readonly string filename; private readonly string filepath; private eLinkState status = eLinkState.ToConfirm; private string fields = ""; public static string PageBase { get { return "https://www.eroticiracconti.it"; } } public static string LinkIdxBase { get { return "https://www.eroticiracconti.it/archivio/Tutto/{ID}"; } } public static string LinkRacBase { get { return "https://www.eroticiracconti.it/racconto/{RacID}"; } } public string ID => "eRac"; public string URL { get; } public string FileName => filename; public string FilePath { get => filepath; } public bool Validate { get { if (URL.StartsWith("https://www.eroticiracconti.it/archivio/Tutto/")) { // URL di tipo Idx, devo lavorare i link nuovo o rilavorare i link più obsoleti di 6 ore. return !App.DB.IsPresent(URL, -6); } else { // URL di tipo Rac, devo lavorare solo i link nuovi return !App.DB.IsPresent(URL); } } } public eLinkState Status { get => status; set { status = value; } } public string Fields { get => fields; set => fields = value; } public cLink_eRac(string Url, string Filename, string fields = "none", string Filepath = "") { this.URL = Url; this.filename = Filename; this.fields = fields; this.filepath = string.IsNullOrEmpty(Filepath) ? Cfg.Settings.PathDownloadWorking : Filepath; this.status = eLinkState.ToConfirm; } public static bool InsertStartLink(Service_Crawler service) { try { int i = 0; string url = LinkIdxBase.Replace("{ID}", i.ToString()); string name = $"_eRac_Idx_{i.ToString("0000")}.html"; service.EnqueueUrl(new cLink_eRac(url, name)); return true; } catch (Exception ex) { Logger.Error("Errore inserendo eRac Start Link - " + ex.Message); return false; } } public cRac EstraiRac(HtmlDocument doc) { /* * Analisi html dei racconti aggiornata al 2025/12/27: * * * Per ciascun file html, si ha un unico racconto che ha un formato interno pari a: * * * * ... * Quante voglie. | Erotici Racconti * * *
*
*
*

{Title}

*

* * * * di
* *
* * * genere
* {Genere} *
*
*

*
* {Trama} *
*
*
* * di
* *
*
* * scritto il
* {Data} *
*
* * 5.1K *
* visite *
*
*
*
* * * 3 *
* voti *
*
* * valutazione
* 2
*
*
*
* *
*
* * il tuo voto
* *
*
* ... *
* ... *
* ... *
* * * * quindi i dati del racconto che si possono estrapolare sono: * Titolo: --> Testo del nodo

{Title}

* SubTitle: --> Sempre Vuoto * Autore: --> Testo del nodo

* Creato: --> Estrarre la parte dal
{Data}
* Tags: --> Testo del nodo

{Genere}

* Trama: --> Testo del nodo
{Trama}
* Notes: --> SrcFile - Nome del file che si analizza * Origini: --> Sempre l'ID della sorgente web * Voto: --> Sempre "Non Letto" * Updated: --> Data attuale * */ cRac rac = new cRac(); HtmlNodeCollection nodes; try { Console.WriteLine($"eRac - Importo: {FileName}"); // Ricavo il nodo radice del racconto:
//HtmlNode root = doc.DocumentNode; HtmlNode root = doc.DocumentNode.SelectSingleNode("//div[@class='story']"); if (root == null) { // racconto non presente return null; } #region Origini: --> Sempre l'ID della sorgente web rac.Origini = new List { ID }; #endregion #region Titolo: --> Testo del nodo

{Title}

try { nodes = root.SelectNodes("//h1[@class='tit']"); rac.Title = cTools.FormattaTesto(nodes[0].InnerText.Trim(), false); if (string.IsNullOrEmpty(rac.Title)) { Logger.Error($"eRac - Import - No Title - {FileName}"); return null; } } catch (Exception ex) { Logger.Error(string.Format("Fallita estrazione titolo dal file {0}\r\n{1}", FileName, ex.Message)); return null; } #endregion #region SubTitle: --> Sempre Vuoto rac.SubTitle = ""; #endregion #region Autore: --> Testo del nodo

try { //nodes = root.SelectNodes("//h2//span[@class='infos']//span[]//a"); nodes = root.SelectNodes("//a[@rel='author']"); List items = new List(); foreach (HtmlNode nd in nodes) { string aut = nd.ParentNode.InnerText.Replace(" ", "").Trim(); if (aut.StartsWith("di")) { items.Add(aut.Replace("di", "").Trim()); } } items.Sort(); string tmp = ""; rac.Autori = new List(); // Rimuovo eventuali duplicati foreach (string aut in items) { if (tmp != aut) { rac.Autori.Add(aut); tmp = aut; } } if (rac.Autori == null || string.IsNullOrEmpty(rac.Autori[0])) { Logger.Error($"eRac - Import - No Autors - {FileName}"); return null; } } catch (Exception ex) { Logger.Error(string.Format("Fallita estrazione autore dal file {0}\r\n{1}", FileName, ex.Message)); return null; } #endregion #region Creato: --> Estrarre la parte dal
rac.Creato = DateTime.Now; try { // Creato --> Estrarre la prima parte dal
{Data}
nodes = root.SelectNodes("//div[@class='infos']//div"); foreach (HtmlNode nd in nodes) { string txt = nd.InnerText.Trim().Replace(" ", ""); if (txt.StartsWith("scritto il")) { CultureInfo culture = CultureInfo.CreateSpecificCulture("it-IT"); DateTimeStyles styles = DateTimeStyles.None; DateTime tmpDT = DateTime.Now; if (!DateTime.TryParse(txt.Replace("scritto il", "").Trim(), culture, styles, out tmpDT)) { Logger.Error($"eRac - Import - No data - {FileName}"); } rac.Creato = tmpDT; break; } } } catch (Exception ex) { Logger.Warning(string.Format("Data non riconosciuta nel file {0}\r\n{1}", FileName, ex.Message)); return null; } #endregion #region Updated: --> Data attuale rac.Updated = DateTime.Now; #endregion #region Tags: --> Testo del nodo

{Genere}

try { //nodes = root.SelectNodes("//h2//span[@class='infos']//span//a"); nodes = root.SelectNodes("//h2//span[@class='infos']//span"); List items = new List(); foreach (HtmlNode nd in nodes) { string txt = nd.InnerText.Trim(); if (txt.StartsWith("genere")) { items.Add(cTools.FormattaTesto(txt.Replace("genere", "").Trim(), false)); } } //items.Sort(); items = items .OrderBy(s => s) // ordina alfabeticamente .Distinct() // elimina duplicati .ToList(); Format_Insert_Tags(rac, items); // rac.Tags = items; } catch (Exception ex) { Logger.Warning(string.Format("Fallita estrazione Tags dal file {0}\r\n{1}", FileName, ex.Message)); return null; } #endregion #region Trama: --> Testo del nodo
a cui si levano i DIV interni. try { nodes = root.SelectNodes("//div[@class='tes']"); foreach (HtmlNode nd in nodes) { foreach (HtmlNode n in nd.ChildNodes) { if (n.Name.ToUpper() == "#TEXT") { rac.Trama += cTools.FormattaTesto(n.InnerText.Trim(), false); } if (n.Name.ToUpper() == "BR") { rac.Trama += "\r"; } if (n.Name.ToUpper() == "DIV") {; } } } } catch (Exception ex) { Logger.Error(string.Format("Fallita estrazione Trama dal file {0}\r\n{1}", FileName, ex.Message)); return null; } #endregion #region Voto: --> Sempre "Non Letto" rac.Voto = "Non Letto"; #endregion #region Notes: --> SrcFile - Nome del file che si analizza rac.Notes = new List(); rac.Notes.Add(new Data.cNota("SrcFile", System.IO.Path.GetFileName(FileName))); #endregion // Formalizzo esito estrazione: if (!rac.Validate()) { rac = null; } } catch (Exception ex) { Logger.Error($"eRac - Import Error - {FileName}: {ex.Message}"); Console.WriteLine($"eRac - Import Error - {FileName}: {ex.Message}"); return null; } return rac; } protected virtual void Format_Insert_Tags(Data.cRac rac, List items) { // Per ora riporto tutti i tag rilevati, mentre in futuro dovrò: // - parsare la lista e formattare correttamente i tag // - ordinare la lista dei tag formattati ed eliminare i duplicati // - se non sono rimasti tag, inserisco il tag generale per indicare che non vi sono tag // - altrimenti inserisco tutti i tag rimasti. rac.Tags = new List(); try { items.Sort(); string tmp = ""; foreach (string item in items) { string it = item.Trim().ToLower(); if (tmp != it) { #region switch per formattare il testo switch (it) { //case "Gay & Bisex": // rac.Tags.Add("Bisex"); // rac.Tags.Add("Gay"); // break; //case "tradimenti": // rac.Tags.Add("Tradimenti"); // break; //case "Lui & Lei": // rac.Tags.Add("Lui & Lei"); // break; //case "trans": // rac.Tags.Add("Trans"); // break; //case "Prime Esperienze": // rac.Tags.Add("Prime Esperienze"); // break; //case "trio": // rac.Tags.Add("Trio"); // break; //case "incesto": // rac.Tags.Add("Incesto"); // break; //case "orge": // rac.Tags.Add("Orge"); // break; //case "bdsm": // rac.Tags.Add("BDSM"); // break; //case "lesbo": // rac.Tags.Add("Lesbo"); // break; //case "Scambio di Coppia": // rac.Tags.Add("Scambio di Coppia"); // break; default: rac.Tags.Add(item); break; } #endregion tmp = it; }//if }//foreach } catch (Exception ex) { Logger.Error(string.Format("Error Code: {0} - Errore in {1}.{2}: {3}", "ER00040", System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType.FullName, System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.Message)); } finally { if (rac.Tags.Count == 0) rac.Tags.Add("_None_"); } }//function #region Gestione Sito eRac private static HttpClient client = null; private static bool autenticated = false; /// /// Indica se siamo autenticati al sito /// public static bool Autenticated => autenticated; /// /// Esegue l'autenticazione al sito: /// - Scarica la pagina di autenticazione /// - analizza quanto scaricato per determinare i campi dinamici 'ts' e 'token' /// - prepara la risposta al form di identificazione /// - invia il POST di autenticazione /// - analizza la risposta per verificare il corretto login /// public static async Task Authenticate() { if (autenticated) { // Autenticazione già fatta, posso proseguire senza altro fare Logger.Warning("eRac - Autenticazione già effettuata."); } try { #region 1) Prepare HTML Client HttpClientHandler _handler = new HttpClientHandler { CookieContainer = new CookieContainer(), UseCookies = true, AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; client = new HttpClient(_handler); client.Timeout = TimeSpan.FromSeconds(60); client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); client.DefaultRequestHeaders.Referrer = new Uri(cLink_eRac.PageBase); #endregion autenticated = true; } catch (Exception ex) { Console.WriteLine($"Errore generale: {ex.Message}"); Logger.Information($"Errore generale: {ex.Message}"); } return autenticated; } /// /// Codice da eseguire per ciascun eRac link: /// - scarico l'url indicato /// - se richiesto lo salvo come file di testo /// - analizzo il documento html ed estraggo tutti gli url ad altre pagine di link /// - analizzo il documento html ed estraggo tutti gli url a racconti /// - analizzo il documento html ed estraggo il testo del racconto /// /// /// public async Task> CrawlPageAsync() { var newlinks = new List(); try { Console.WriteLine($"eRac - Scarico: {URL}"); HtmlDocument tdoc = null; string thtml = ""; #region ToConfirm if (Status == eLinkState.ToConfirm) { // Eseguo la verifica se link da downloadare Status = eLinkState.ToDownload; } #endregion #region ToDownload if (Status == eLinkState.ToDownload) { Status = eLinkState.OnDownload; } #endregion #region OnDownload if (Status == eLinkState.OnDownload) { // Scarico il file var resp = await client.GetAsync(URL); if (!resp.IsSuccessStatusCode) { Console.WriteLine($"eRac - Errore HTTP {(int)resp.StatusCode} {resp.ReasonPhrase} per {URL}"); Status = eLinkState.Error; //App.DB.LinkLog(lk); return newlinks; } // Scarico la pagina thtml = await client.GetStringAsync(URL); tdoc = new HtmlDocument(); tdoc.LoadHtml(thtml); // Salvo il file downloadato //SaveToFile(thtml, lk.FilePath, lk.FileName); Status = eLinkState.ToAnalize; } #endregion #region ToAnalize { if (Status == eLinkState.ToAnalize) { List tmpLk = new List(); #region Estraggo gli url ad indici: { /* * Gli url delle pagine di indice sono del tipo: * * * */ try { // Estraggo i link alle altre pagine: var tnodes = tdoc.DocumentNode.SelectNodes("//div[@class='pagination']//a[@href]"); if (tnodes != null) { foreach (var link in tnodes) { var href = link.GetAttributeValue("href", ""); if (!string.IsNullOrEmpty(href) && href.StartsWith("/archivio/Tutto/")) tmpLk.Add(href); //tmpLk.Add(cLink_eRac.PageBase + href); } } // Rimuove duplicati e ordina alfabeticamente tmpLk = tmpLk .OrderBy(s => s) // ordina alfabeticamente .Distinct() // elimina duplicati .ToList(); // Inserisco i link trovati foreach (string ilk in tmpLk) { string url = cLink_eRac.PageBase + ilk; int offset = int.Parse(ilk.Replace("/archivio/Tutto/", "")); string name = $"_eRac_Idx_{offset.ToString("0000")}.html"; newlinks.Add(new cLink_eRac(url, name)); } } catch (Exception ex) { Logger.Error("Errore analisi Idx link dal " + FileName + " - " + ex.Message); cTools.SaveToFile(thtml, Cfg.Settings.PathSrcErrOnExtract, FileName); } } #endregion #region Estraggo gli url a racconti: { /* * * I link ai racconti sono dei pulsanti così formati: * * */ try { // Estraggo i link ai racconti: tmpLk = new List(); // Estraggo i link alle altre pagine: var tnodes = tdoc.DocumentNode.SelectNodes("//a[@class='rac']"); if (tnodes != null) { foreach (var link in tnodes) { var href = link.GetAttributeValue("href", ""); if (!string.IsNullOrEmpty(href) && href.StartsWith("/racconto/")) tmpLk.Add(href); //tmpLk.Add(cLink_eRac.PageBase + href); } } // Rimuove duplicati e ordina alfabeticamente tmpLk = tmpLk .OrderBy(s => s) // ordina alfabeticamente .Distinct() // elimina duplicati .ToList(); // Inserisco i link trovati foreach (string ilk in tmpLk) { string url = cLink_eRac.PageBase + ilk; string name = ilk.Replace("/racconto/", "eRac_").Replace(@"/", "_") + ".html"; newlinks.Add(new cLink_eRac(url, name)); } } catch (Exception ex) { Logger.Error("Errore analisi Idx link dal " + FileName + " - " + ex.Message); cTools.SaveToFile(thtml, Cfg.Settings.PathSrcErrOnExtract, FileName); } } #endregion #region Estraggo il racconto dal file e lo inserisco nel DB if (FileName.StartsWith("eRac_")) { // Link ad un racconto cRac rac = EstraiRac(tdoc); if (rac != null) { // Racconto estratto correttamente, lo salvo sul DB int res = App.DB.ImportRac(rac); if (res > 0) { // Racconto salvato correttamente sul DB cTools.SaveToFile(thtml, Cfg.Settings.PathSrcDone, FileName); } else { // Errore nel salvataggio sul DB cTools.SaveToFile(thtml, Cfg.Settings.PathSrcErrOnSave, FileName); } } else { // Estrazione del racconto fallita cTools.SaveToFile(thtml, Cfg.Settings.PathSrcErrOnExtract, FileName); } } else { // Link ad un indice cTools.SaveToFile(thtml, Cfg.Settings.PathIdxDone, FileName); } #endregion Status = eLinkState.End; App.DB.LinkLog(this); } } #endregion #region End if (Status == eLinkState.End) { } #endregion #region Error if (Status == eLinkState.Error) { } #endregion #region Abort if (Status == eLinkState.Abort) { } #endregion #region Cancel if (Status == eLinkState.Cancel) { } #endregion Console.WriteLine("Link.Count=" + newlinks.Count.ToString("00")); } catch (Exception ex) { Console.WriteLine($"Errore con {URL}: {ex.Message}"); Status = eLinkState.Error; App.DB.LinkLog(this); } return newlinks; } #endregion }//class cLink_eRac }//namespace A69WD.Net