package main import ( "backend/config" "backend/parser" "backend/typesdefs" "backend/utils" "fmt" "log" "reflect" "regexp" "slices" "strconv" "strings" "time" "github.com/schollz/progressbar/v3" ) type MiogestHandler struct { Caratterisiche typesdefs.ListaCaratteristiche Categorie []typesdefs.Categoria AnnunciXML typesdefs.AnnunciXML AnnunciParsed typesdefs.AnnunciParsed annunci_cache_expires time.Time vars_cache_expires time.Time } func NewMiogestHandler() *MiogestHandler { return &MiogestHandler{ Caratterisiche: typesdefs.ListaCaratteristiche{}, Categorie: nil, AnnunciXML: typesdefs.AnnunciXML{}, AnnunciParsed: typesdefs.AnnunciParsed{}, annunci_cache_expires: time.Now(), vars_cache_expires: time.Now(), } } // ANNUNCI FROM MIOGEST func (m *MiogestHandler) GetAnnunci() typesdefs.AnnunciXML { if len(m.AnnunciXML.Annuncio) == 0 || time.Now().After(m.annunci_cache_expires) { m.GetAnnunciFromMiogest() } return m.AnnunciXML } func (m *MiogestHandler) GetAnnunciFromMiogest() { m.AnnunciXML = utils.GetDataXML[typesdefs.AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml") m.annunci_cache_expires = time.Now().Add(1 * time.Hour) } // CATEGORIE AND CARATTERISTICHE FROM MIOGEST func (m *MiogestHandler) InitDefaults() { m.GenerateCaratteristiche() m.GenerateCategorie() } func (m *MiogestHandler) GetCaratteristiche() typesdefs.ListaCaratteristiche { if len(m.Caratterisiche.Caratteristiche) == 0 || time.Now().After(m.vars_cache_expires) { m.GenerateCaratteristiche() } return m.Caratterisiche } func (m *MiogestHandler) GetCategorie() []typesdefs.Categoria { if len(m.Categorie) == 0 || time.Now().After(m.vars_cache_expires) { m.GenerateCategorie() } return m.Categorie } func (m *MiogestHandler) GenerateCaratteristiche() { var result = utils.GetDataXML[typesdefs.DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede") var array typesdefs.ListaCaratteristiche for _, tag := range result.Scheda { var c = typesdefs.Caratteristica{Id: tag.Id, Tagxml: tag.Tagxml} array.Caratteristiche = append(array.Caratteristiche, c) } m.Caratterisiche = array m.vars_cache_expires = time.Now().Add(1 * time.Hour) } func (m *MiogestHandler) GenerateCategorie() { var result = utils.GetDataXML[typesdefs.DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie") var listaCategorie []typesdefs.Categoria for _, elem := range result.Cat { var c = typesdefs.Categoria{Id: elem.ID, Nome: elem.Nome} listaCategorie = append(listaCategorie, c) } m.Categorie = listaCategorie m.vars_cache_expires = time.Now().Add(1 * time.Hour) } // ANNUNCI PARSED func (m *MiogestHandler) GetAnnunciParsed(imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess, forceMediaStale bool) typesdefs.AnnunciParsed { // salvo il parsing nello struct nel caso voglio implementare un caching e non fare il parsing ad ogni chiamata m.AnnunciParsed = m.ParseAnnunci("", imgPool, videoPool, forceMediaStale) return m.AnnunciParsed } // versione per singolo annuncio func (m *MiogestHandler) GetAnnuncioParsed(codFilter string, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess) typesdefs.AnnunciParsed { data := m.ParseAnnunci(codFilter, imgPool, videoPool, false) if len(data.Annuncio) == 0 { return typesdefs.AnnunciParsed{} } return data } func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess, forceMediaStale bool) typesdefs.AnnunciParsed { annunci := m.GetAnnunci() if len(annunci.Annuncio) == 0 { return typesdefs.AnnunciParsed{} } if codFilter != "" { idx := slices.IndexFunc(annunci.Annuncio, func(a typesdefs.AnnuncioXML) bool { return *a.Codice == codFilter }) if idx == -1 { return typesdefs.AnnunciParsed{} } annunci.Annuncio = []typesdefs.AnnuncioXML{annunci.Annuncio[idx]} } image_codes_to_process := []string{} if config.Cfg.Images.Enabled { var err error if forceMediaStale { // process all images image_codes_to_process = make([]string, 0, len(annunci.Annuncio)) for _, annuncio := range annunci.Annuncio { if annuncio.Codice != nil { image_codes_to_process = append(image_codes_to_process, *annuncio.Codice) } } } else { // process only new or updated images image_codes_to_process, err = CodiciToProcessImages(&annunci) if err != nil { log.Fatalf("Failed to find images to update: %v", err.Error()) } } if codFilter != "" { // if processing single codice, ensure it's in the list if !slices.Contains(image_codes_to_process, codFilter) { image_codes_to_process = append(image_codes_to_process, codFilter) } } err = ForceClearImages(image_codes_to_process) if err != nil { log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error()) } } video_codes_to_process := []string{} if config.Cfg.Videos.Enabled { var err error if forceMediaStale { // process all videos video_codes_to_process = make([]string, 0, len(annunci.Annuncio)) for _, annuncio := range annunci.Annuncio { if annuncio.Codice != nil { video_codes_to_process = append(video_codes_to_process, *annuncio.Codice) } } } else { // process only new or updated videos video_codes_to_process, err = CodiciToProcessVideos(&annunci) if err != nil { log.Fatalf("Failed to find videos to update: %v", err.Error()) } } if codFilter != "" { // if processing single codice, ensure it's in the list if !slices.Contains(video_codes_to_process, codFilter) { video_codes_to_process = append(video_codes_to_process, codFilter) } } err = ForceClearVideos(video_codes_to_process) if err != nil { log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error()) } } totalAnnunci := len(annunci.Annuncio) bar := progressbar.Default(int64(totalAnnunci), "Parsing annunci") var AnnunciArray typesdefs.AnnunciParsed AnnunciArray.Annuncio = make([]typesdefs.AnnuncioParsed, 0, len(annunci.Annuncio)) m.InitDefaults() for i := range annunci.Annuncio { imgToUpdate := slices.Contains(image_codes_to_process, *annunci.Annuncio[i].Codice) updateVideos := slices.Contains(video_codes_to_process, *annunci.Annuncio[i].Codice) result := extractData_update(&annunci.Annuncio[i], imgToUpdate, updateVideos, m, imgPool, videoPool) AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result) bar.Add(1) } bar.Finish() fmt.Println() return AnnunciArray } var re = regexp.MustCompile(`[^+\d]`) func boolUpd(b bool) *bool { return &b } func strUpd(s string) *string { return &s } func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, updateVideos bool, m *MiogestHandler, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess) typesdefs.AnnuncioParsed { var tmp typesdefs.AnnuncioParsed = typesdefs.AnnuncioParsed{} tmp.Codice = utils.RemoveWhitespace(utils.GetStringValue(annuncio.Codice)) processLocatore(&tmp, annuncio) processIndirizzo(&tmp, annuncio) tmp.Tipo = parser.ParseTipologia(annuncio.Tipologia, annuncio.Tipologia2) tmp.Consegna = parser.ParseConsegna(annuncio.Consegna) processCategorie(&tmp, annuncio, m) processDatiImmobile(&tmp, annuncio) tmp.Stato = annuncio.Stato tmp.Homepage = boolUpd(false) if annuncio.HomePage != nil { if *annuncio.HomePage == "si" { tmp.Homepage = boolUpd(true) } } layout := "20060102150405" if annuncio.Creato != nil { t, err := time.Parse(layout, *annuncio.Creato) if err != nil { tmp.Creato_il = nil } else { tmp.Creato_il = &t } } if annuncio.Modifica != nil { t, err := time.Parse(layout, *annuncio.Modifica) if err != nil { tmp.Modificato_il = nil } else { tmp.Modificato_il = &t } } tmp.Titolo_it = annuncio.Titolo tmp.Titolo_en = annuncio.TitoloEn tmp.Desc_it = annuncio.Descrizione tmp.Desc_en = annuncio.DescrizioneEn if annuncio.Dettaglio != nil { disp, parm, pers, err := parser.ParseDettaglio(*annuncio.Dettaglio, tmp.Consegna) if err == nil { tmp.Disponibile_da = disp tmp.Permanenza = parm tmp.Persone = pers } } tmp.Web = boolUpd(true) if updateVideos { AddToVideoProcessing(&tmp, annuncio, videoPool) } if annuncio.Video != nil { ext_videos := []string{} for _, v := range *annuncio.Video { if strings.Contains(v, "youtu") { ext_videos = append(ext_videos, v) } tmp.External_videos = &ext_videos } } else { tmp.External_videos = &[]string{} } if annuncio.Accessori != nil { tmp.Accessori = annuncio.Accessori } tmpcaratt := parseCaratteristiche(annuncio, m) tmp.Caratteristiche = &tmpcaratt if updateImages { AddToImageProcessing(&tmp, annuncio, imgPool) } return tmp } func processLocatore(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) { if annuncio.Clienti == nil || annuncio.Clienti.Cliente == nil { return } tmp.Email = parser.ParseEmail(annuncio.Clienti.Cliente.Email1) var number string for _, tel := range []*string{annuncio.Clienti.Cliente.Tel1, annuncio.Clienti.Cliente.Tel2, annuncio.Clienti.Cliente.Tel3} { if tel != nil { cleanedTel := re.ReplaceAllString(*tel, "") if cleanedTel != "" { number = cleanedTel break } } } tmp.Numero = &number if annuncio.Clienti.Cliente.Cognome != nil && annuncio.Clienti.Cliente.Nome != nil { tmp.Locatore = strUpd(fmt.Sprintf("%s %s", utils.MakeUppercase(*annuncio.Clienti.Cliente.Cognome), utils.MakeUppercase(*annuncio.Clienti.Cliente.Nome))) } tmp.Idlocatore = annuncio.Clienti.Cliente.Id } func processIndirizzo(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) { tmp.Indirizzo = strUpd(utils.MakeUppercase(utils.GetStringValue(annuncio.Indirizzo))) tmp.Civico = annuncio.Civico tmp.Comune = strUpd(utils.MakeUppercase(utils.GetStringValue(annuncio.Comune))) tmp.Cap = annuncio.Cap tmp.Provincia = annuncio.Provincia tmp.Regione = annuncio.Regione tmp.Lat = annuncio.Latitudine tmp.Lon = annuncio.Longitudine tmp.Indirizzo_secondario = strUpd(utils.MakeUppercase(utils.GetStringValue(annuncio.IndirizzoSecondario))) tmp.Civico_secondario = annuncio.CivicoSecondario tmp.Lat_secondario = annuncio.LatitudineSecondario tmp.Lon_secondario = annuncio.LongitudineSecondario } func processCategorie(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML, m *MiogestHandler) { if annuncio.Categoria == nil { return } resultArray := make([]string, 0) cats := *annuncio.Categoria defaultCategorie := m.GetCategorie() for _, cat := range cats { for _, catdef := range defaultCategorie { if cat == catdef.Id { resultArray = append(resultArray, catdef.Nome) } } } if tmp.Categorie == nil { tmp.Categorie = &[]string{} } *tmp.Categorie = resultArray } func processDatiImmobile(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) { tmpanno := utils.GetStringValue(annuncio.Anno) if tmpanno != "0" && tmpanno != "" { tmp.Anno = &tmpanno } tmp.Prezzo = parser.ParsePrezzo(annuncio.Prezzo) tmp.Classe = annuncio.Classe if annuncio.Mq != nil { replaced := strings.ReplaceAll(*annuncio.Mq, ",", ".") if s, err := strconv.ParseFloat(replaced, 64); err == nil { tmp.Mq = &s } } tmp.Piano = annuncio.Piano tmp.Piano_palazzo = annuncio.PianiCondominio tmp.Unita_condominio = annuncio.UnitaCondominio tmp.Numero_vani = annuncio.Vani tmp.Numero_camere = annuncio.Camere tmp.Numero_bagni = annuncio.Bagni tmp.Numero_balconi = annuncio.Balconi tmp.Numero_terrazzi = annuncio.Terrazzi tmp.Numero_box = annuncio.Box tmp.Numero_postiauto = annuncio.PostiAuto } func parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) map[string]interface{} { if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct { return nil } tmpCaratteristiche := make(map[string]interface{}) val := reflect.ValueOf(wrkrecord).Elem() numField := val.NumField() for i := 0; i < numField; i++ { if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") { nameField := val.Type().Field(i).Name valueField := val.Field(i) if valueField.IsValid() && !valueField.IsNil() { tmpCaratteristiche[nameField] = valueField.Interface() } else { tmpCaratteristiche[nameField] = nil } } } for _, scheda := range m.GetCaratteristiche().Caratteristiche { if _, ok := tmpCaratteristiche[scheda.Tagxml]; !ok { tmpCaratteristiche[scheda.Tagxml] = nil } } return tmpCaratteristiche }