package main import ( "fmt" "io" "log" "net/http" "os" "path/filepath" "reflect" "regexp" "slices" "strconv" "strings" "sync" "time" ) type MiogestHandler struct { Caratterisiche ListaCaratteristiche Categorie []Categoria AnnunciXML AnnunciXML AnnunciParsed AnnunciParsed annunci_cache_expires time.Time vars_cache_expires time.Time } func NewMiogestHandler() *MiogestHandler { return &MiogestHandler{ Caratterisiche: ListaCaratteristiche{}, Categorie: nil, AnnunciXML: AnnunciXML{}, AnnunciParsed: AnnunciParsed{}, annunci_cache_expires: time.Now(), vars_cache_expires: time.Now(), } } // ANNUNCI FROM MIOGEST func (m *MiogestHandler) GetAnnunci() 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 = GetDataXML[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() ListaCaratteristiche { if len(m.Caratterisiche.Caratteristiche) == 0 || time.Now().After(m.vars_cache_expires) { m.GenerateCaratteristiche() } return m.Caratterisiche } func (m *MiogestHandler) GetCategorie() []Categoria { if len(m.Categorie) == 0 || time.Now().After(m.vars_cache_expires) { m.GenerateCategorie() } return m.Categorie } func (m *MiogestHandler) GenerateCaratteristiche() { var result = GetDataXML[DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede") var array ListaCaratteristiche for _, tag := range result.Scheda { var c = 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 = GetDataXML[DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie") var listaCategorie []Categoria for _, elem := range result.Cat { var c = 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() AnnunciParsed { m.ParseAnnunci() return m.AnnunciParsed } func (m *MiogestHandler) ParseAnnunci() { annunci := m.GetAnnunci() cod_image_to_update := []string{} if imageOption { var err error cod_image_to_update, err = ImageManager.FindIsUpdate(&annunci) if err != nil { log.Fatalf("Failed to find images to update: %v", err.Error()) } if err := ImageManager.ClearImages(cod_image_to_update); err != nil { log.Fatalf("Failed to clear images: %v", err.Error()) } ImageManager.InsertImagesRef(&annunci) } var AnnunciArray AnnunciParsed AnnunciArray.Annuncio = make([]AnnuncioParsed, 0, len(annunci.Annuncio)) Miogest.InitDefaults() var wg sync.WaitGroup wg.Add(len(annunci.Annuncio)) sem := make(chan struct{}, concurrentImages) //Limit concurrent goroutines lista_codici := []string{} for i := range annunci.Annuncio { lista_codici = append(lista_codici, RemoveWhitespace(*annunci.Annuncio[i].Codice)) sem <- struct{}{} go func(i int) { defer wg.Done() defer func() { <-sem }() result := extractData_update(&annunci.Annuncio[i], contains(cod_image_to_update, *annunci.Annuncio[i].Codice)) AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result) }(i) } wg.Wait() if imageOption { for _, codice := range lista_codici { err := folderCleanup(codice) if err != nil { log.Fatalf("Failed to cleanup folder: %v", err.Error()) } } } m.AnnunciParsed = AnnunciArray } var re = regexp.MustCompile(`[^+\d]`) func boolUpd(b bool) *bool { return &b } func strUpd(s string) *string { return &s } func extractData_update(annuncio *AnnuncioXML, updateImages bool) AnnuncioParsed { var tmp AnnuncioParsed = AnnuncioParsed{} tmp.Codice = RemoveWhitespace(GetStringValue(annuncio.Codice)) processLocatore(&tmp, annuncio) processIndirizzo(&tmp, annuncio) processTipologia(&tmp, annuncio) tmp.Consegna = parseConsegna(annuncio.Consegna) processCategorie(&tmp, annuncio) 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 tmp.Web = boolUpd(true) videos := []string{} if annuncio.Video != nil { videos = *annuncio.Video } miogestVideos := []string{} if annuncio.AMMedias != nil { for _, media := range *annuncio.AMMedias { if media.AMVideo != nil && media.AMVideo.Url != nil { miogestVideos = append(miogestVideos, *media.AMVideo.Url) } } } concatenatedVideos := slices.Concat(videos, miogestVideos) tmp.Url_video = &concatenatedVideos if annuncio.Accessori != nil { tmp.Accessori = annuncio.Accessori } tmpcaratt := parseCaratteristiche(annuncio) tmp.Caratteristiche = &tmpcaratt if updateImages { processImages(&tmp, annuncio) } folders := GetPathDirsArray(filepath.Join(imgbasepath, tmp.Codice)) for _, folder := range folders { folderpath := filepath.Join(filepath.Join(imgbasepath, tmp.Codice), folder) files, err := os.ReadDir(folderpath) if err != nil { panic(err) } for _, file := range files { if strings.Contains(file.Name(), "webp") && !strings.HasPrefix(file.Name(), "thumbnail") { tmpImgUrl := tmp.Codice + "/" + folder if tmp.Url_foto == nil { tmp.Url_foto = &[]string{} } *tmp.Url_foto = append(*tmp.Url_foto, tmpImgUrl) } } } return tmp } func processLocatore(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { if annuncio.Clienti == nil || annuncio.Clienti.Cliente == nil { return } tmp.Email = 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", MakeUppercase(*annuncio.Clienti.Cliente.Cognome), MakeUppercase(*annuncio.Clienti.Cliente.Nome))) } tmp.Idlocatore = annuncio.Clienti.Cliente.Id } func processIndirizzo(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { tmp.Indirizzo = strUpd(MakeUppercase(GetStringValue(annuncio.Indirizzo))) tmp.Civico = annuncio.Civico tmp.Comune = strUpd(MakeUppercase(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(MakeUppercase(GetStringValue(annuncio.IndirizzoSecondario))) tmp.Civico_secondario = annuncio.CivicoSecondario tmp.Lat_secondario = annuncio.LatitudineSecondario tmp.Lon_secondario = annuncio.LongitudineSecondario } func processTipologia(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { if annuncio.Tipologia == nil { tmp.Tipo = strUpd("Errore") return } if *annuncio.Tipologia == "V" { tmp.Tipo = strUpd("Vendita") return } if *annuncio.Tipologia == "I" { tmp.Tipo = strUpd("Cessione") return } if *annuncio.Tipologia == "A" { if *annuncio.Tipologia2 == "Residenziale" || *annuncio.Tipologia2 == "Turistico" || *annuncio.Tipologia2 == "Stanza" { tmp.Tipo = strUpd("Transitorio") } else { tmp.Tipo = strUpd("Stabile") } return } tmp.Tipo = strUpd("Errore") } func processCategorie(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { if annuncio.Categoria == nil { return } resultArray := make([]string, 0) cats := *annuncio.Categoria defaultCategorie := Miogest.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 prezzoParse(prezzo *string) int { zero := 0 if prezzo == nil { return zero } f, err := strconv.ParseFloat(strings.TrimSpace(strings.ReplaceAll(*prezzo, ",", ".")), 64) if err != nil { log.Printf("Failed to parse price: %v", err) return zero } prezzoInt := int(f * 100) // Convert to cents return prezzoInt } func processDatiImmobile(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { tmpanno := GetStringValue(annuncio.Anno) if tmpanno != "0" && tmpanno != "" { tmp.Anno = &tmpanno } tmp.Prezzo = prezzoParse(annuncio.Prezzo) tmp.Classe = annuncio.Classe if annuncio.Mq != nil { replaced := strings.ReplaceAll(*annuncio.Mq, ",", "") tmp.Mq = &replaced } 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 processImages(tmp *AnnuncioParsed, annuncio *AnnuncioXML) { path := filepath.Join(imgbasepath, tmp.Codice) err := os.MkdirAll(path, 0755) if err != nil { log.Printf("Failed to create directory: %v", err) panic(err) } var wg sync.WaitGroup sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines nfoto := 0 var fotos []string if annuncio.Foto != nil { fotos = *annuncio.Foto } for _, url := range fotos { fmt.Println("Processing image", url) imgpath := filepath.Join(path, fmt.Sprintf("%d", nfoto)) if _, err := os.Stat(imgpath); os.IsNotExist(err) { os.Mkdir(imgpath, 0755) } filename := filepath.Join(imgpath, fmt.Sprintf("%s_%d%s", tmp.Codice, nfoto, filepath.Ext(url))) wg.Add(1) sem <- struct{}{} go func(url, filename string) { defer wg.Done() defer func() { <-sem }() downloadImage(url, filename) }(url, filename) nfoto++ } wg.Wait() processWebpImages(path) } func downloadImage(url, filename string) { response, err := http.Get(url) if err != nil { log.Fatalf("Failed to download image: %v", err) return } defer response.Body.Close() file, err := os.Create(filename) if err != nil { log.Fatalf("Failed to create file: %v", err) return } defer file.Close() _, err = io.Copy(file, response.Body) if err != nil { log.Fatalf("Failed to save image: %v", err) } } func processWebpImages(path string) { array, err := GetPathsArray(path) if err != nil { log.Fatal(err) } var wg sync.WaitGroup sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines for _, img := range array { wg.Add(1) sem <- struct{}{} go func(img string) { defer wg.Done() defer func() { <-sem }() err := Conversion(img) if err != nil { log.Fatal(err) } }(img) } wg.Wait() } func folderCleanup(codice string) error { folders := GetPathDirsArray(filepath.Join(imgbasepath, codice)) non_webp := []string{} for _, folder := range folders { folderpath := filepath.Join(imgbasepath, codice, folder) files, err := os.ReadDir(folderpath) if err != nil { return err } for _, file := range files { if !strings.Contains(file.Name(), "webp") { non_webp = append(non_webp, filepath.Join(folderpath, file.Name())) } } } for _, file := range non_webp { err := os.Remove(file) if err != nil { return err } } return nil } func parseCaratteristiche(wrkrecord *AnnuncioXML) 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 Miogest.GetCaratteristiche().Caratteristiche { if _, ok := tmpCaratteristiche[scheda.Tagxml]; !ok { tmpCaratteristiche[scheda.Tagxml] = nil } } return tmpCaratteristiche } var mesiMapping = map[int]string{ 1: "Gennaio", 2: "Febbraio", 3: "Marzo", 4: "Aprile", 5: "Maggio", 6: "Giugno", 7: "Luglio", 8: "Agosto", 9: "Settembre", 10: "Ottobre", 11: "Novembre", 12: "Dicembre", } func parseConsegna(original *string) *int { currentMonth := int(time.Now().Month()) if original == nil { return nil } if *original == "Libero" { return ¤tMonth } else { for k, v := range mesiMapping { if *original == v { return &k } } } return ¤tMonth }