···11# 🤬 Assembleia Legislativa Desanda Catarina
2233-1. Baixar os [dados do TSE de candiaturas para as eleições gerais de 2022](https://dadosabertos.tse.jus.br/dataset/candidatos-2022) e salvar em `tse/`:
44- 1. `consulta_cand_2022.zip`
55- 1. `foto_cand2022_SC_div.zip`
66- 1. `rede_social_candidato_2022_SC.zip`
77-2. Compilar com, por exemplo, `go build -o /usr/local/bin/desanda`
88-3. Gerar o site estático em `site/` com `desanda`
33+1. Compilar com, por exemplo, `go build -o /usr/local/bin/desanda`
44+2. Gerar o site estático em `site/` com `desanda`
+93-29
main.go
···290290 return "fas fa-globe"
291291}
292292293293-func socialMediaFor(m *map[string][]string, client *http.Client, id string) ([]SocialMedia, error) {
294294- var urls []SocialMedia
293293+func socialMediaFor(urls []string, client *http.Client, id string) ([]SocialMedia, error) {
295294 var g errgroup.Group
296295 ch := make(chan string)
297297- for _, url := range (*m)[id] {
296296+ done := make(chan struct{})
297297+ var out []SocialMedia
298298+ go func() {
299299+ defer close(done)
300300+ for url := range ch {
301301+ out = append(out, SocialMedia{URL: url, Icon: socialIcon(url)})
302302+ }
303303+ }()
304304+ for _, url := range urls {
298305 g.Go(func() error {
299306 resp, err := client.Head(url)
300307 if err != nil {
301308 var nf *net.DNSError
302309 if errors.As(err, &nf) && nf.IsNotFound {
310310+ return nil
311311+ }
312312+ if _, ok := err.(net.Error); ok {
303313 return nil
304314 }
305315 return fmt.Errorf("could not check %s: %w", url, err)
···316326 return nil
317327 })
318328 }
319319- go func() {
320320- defer close(ch)
321321- if err := g.Wait(); err != nil {
322322- slog.Warn("error waiting for social media checks", "error", err)
329329+ err := g.Wait()
330330+ close(ch)
331331+ if err != nil {
332332+ return nil, err
333333+ }
334334+ <-done
335335+ return out, nil
336336+}
337337+338338+func downloadFile(client *http.Client, url, dir string) error {
339339+ slog.Info("Downloading", "url", url)
340340+ defer slog.Info("Finished", "url", url)
341341+ resp, err := client.Get(url)
342342+ if err != nil {
343343+ return err
344344+ }
345345+ defer func() {
346346+ if err := resp.Body.Close(); err != nil {
347347+ slog.Warn("could not close response body", "url", url, "error", err)
323348 }
324349 }()
325325- for url := range ch {
326326- urls = append(urls, SocialMedia{URL: url, Icon: socialIcon(url)})
350350+ if resp.StatusCode != http.StatusOK {
351351+ return fmt.Errorf("unexpected status for %s: %s", url, resp.Status)
327352 }
328328- return urls, nil
353353+ pth := filepath.Join(dir, filepath.Base(url))
354354+ f, err := os.Create(pth)
355355+ if err != nil {
356356+ return err
357357+ }
358358+ defer func() {
359359+ if err := f.Close(); err != nil {
360360+ slog.Warn("could not close file", "path", pth, "error", err)
361361+ }
362362+ }()
363363+ _, err = io.Copy(f, resp.Body)
364364+ return err
329365}
330366331367func main() {
···333369 defer func() {
334370 slog.Info("site built", "elapsed", time.Since(init))
335371 }()
372372+ tmp, err := os.MkdirTemp("", "desanda-*")
373373+ if err != nil {
374374+ log.Fatal(err)
375375+ }
376376+ defer func() {
377377+ if err := os.RemoveAll(tmp); err != nil {
378378+ slog.Warn("could not remove temp dir", "path", tmp, "error", err)
379379+ }
380380+ }()
381381+ client := &http.Client{
382382+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
383383+ return http.ErrUseLastResponse
384384+ },
385385+ Transport: &http.Transport{
386386+ MaxConnsPerHost: 8,
387387+ TLSClientConfig: &tls.Config{
388388+ InsecureSkipVerify: true,
389389+ },
390390+ },
391391+ }
392392+ var d errgroup.Group
393393+ urls := []string{
394394+ "https://cdn.tse.jus.br/estatistica/sead/odsele/consulta_cand/consulta_cand_2022.zip",
395395+ "https://cdn.tse.jus.br/estatistica/sead/eleicoes/eleicoes2022/fotos/foto_cand2022_SC_div.zip",
396396+ "https://cdn.tse.jus.br/estatistica/sead/odsele/consulta_cand/rede_social_candidato_2022_SC.zip",
397397+ }
398398+ for _, u := range urls {
399399+ d.Go(func() error { return downloadFile(client, u, tmp) })
400400+ }
401401+ if err := d.Wait(); err != nil {
402402+ log.Fatal(err)
403403+ }
336404 slugs, err := readData()
337405 if err != nil {
338406 log.Fatal(err)
339407 }
340340- urls, err := csvFromZip("tse/rede_social_candidato_2022_SC.zip", "rede_social_candidato_2022_SC.csv")
408408+ all, err := csvFromZip(filepath.Join(tmp, "rede_social_candidato_2022_SC.zip"), "rede_social_candidato_2022_SC.csv")
341409 if err != nil {
342410 log.Fatal(err)
343411 }
344412 m := make(map[string][]string)
345345- for i, row := range urls {
413413+ for i, row := range all {
346414 if i == 0 {
347415 continue
348416 }
349417 m[row[8]] = append(m[row[8]], row[10])
350418 }
351351- rows, err := csvFromZip("tse/consulta_cand_2022.zip", "consulta_cand_2022_SC.csv")
419419+ rows, err := csvFromZip(filepath.Join(tmp, "consulta_cand_2022.zip"), "consulta_cand_2022_SC.csv")
352420 if err != nil {
353421 log.Fatal(err)
354422 }
355355- client := &http.Client{
356356- CheckRedirect: func(req *http.Request, via []*http.Request) error {
357357- return http.ErrUseLastResponse
358358- },
359359- Transport: &http.Transport{
360360- MaxConnsPerHost: 8,
361361- TLSClientConfig: &tls.Config{
362362- InsecureSkipVerify: true,
363363- },
364364- },
365365- }
366423 var g errgroup.Group
367424 g.SetLimit(16)
368425 idIdx := 15
···376433 for p := range ch {
377434 t++
378435 ps = append(ps, p)
379379- fmt.Printf("%02d. %s\n", t, p.Name)
436436+ slog.Info("Matched", "count", t, "name", p.Name)
380437 }
381438 sort.Slice(ps, func(i, j int) bool {
382439 return ps[i].Name < ps[j].Name
···398455 if len(v) == 0 {
399456 return nil
400457 }
401401- pic, err := copyPhotoFrom(id, "tse/foto_cand2022_SC_div.zip")
458458+ pic, err := copyPhotoFrom(id, filepath.Join(tmp, "foto_cand2022_SC_div.zip"))
402459 if err != nil {
403460 return fmt.Errorf("could not get photo for %s (%s): %w", n, id, err)
404461 }
405462 if pic == "" {
406463 return fmt.Errorf("could not get photo for %s (%s)", n, id)
407464 }
408408- sm, err := socialMediaFor(&m, client, id)
409409- if err != nil {
410410- return fmt.Errorf("could not get social media for %s (%s): %w", n, id, err)
465465+ var sm []SocialMedia
466466+ urls, ok := m[id]
467467+ if ok {
468468+ sm, err = socialMediaFor(urls, client, id)
469469+ if err != nil {
470470+ return fmt.Errorf("could not get social media for %s (%s): %w", n, id, err)
471471+ }
411472 }
412473 p := Person{
413474 id,
···427488 }
428489 close(ch)
429490 <-done
491491+ if len(ps) == 0 {
492492+ log.Fatal("could not find data to populate the site")
493493+ }
430494 if err := os.MkdirAll("site", 0755); err != nil {
431495 log.Fatal(err)
432496 }