shithub: mkfaces

Download patch

ref: b3df5eac3f2ebcd868ebfccb096da350df1f13bc
parent: 5695059046518899c4c89cc6cc1a8032cdc9eca0
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Thu Dec 5 06:03:17 EST 2019

first revision

--- /dev/null
+++ b/README.md
@@ -1,0 +1,24 @@
+# mkfaces
+
+Some kind of Gravatar `face(6)` fetcher for Plan 9?
+
+Goes through `/mail/fs/*` and tries to create a face based on "from" of
+each email.  Stores information according to `face(6)`. It keeps the old
+information intact so it should be considered safe to use on top of
+whatever faces one already had in `/usr/$user/lib/face`.
+
+In addition it ignores "machine/user" if it matches any of regexps
+stored in `/usr/$user/lib/face/.ignorelist`.
+
+Example of `.ignorelist`:
+
+```
+meetup.com
+slack.com
+amazon.com
+```
+
+Maybe I will have time to rewrite it in `rc` later, for now one can
+build it this way: `GOOS=plan9 GOARCH=amd64 go build`.
+
+This sucks.
--- /dev/null
+++ b/main.go
@@ -1,0 +1,215 @@
+package main
+
+import (
+	"bytes"
+	"crypto/md5"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"os/exec"
+	"regexp"
+	"sort"
+	"strings"
+)
+
+var (
+	httpClient = &http.Client{}
+	base       = "/mail/fs"
+)
+
+func main() {
+	home, err := os.UserHomeDir()
+	if err != nil {
+		log.Fatal(err)
+	}
+	faceBase := home + "/lib/face"
+	outBase := faceBase + "/48x48x8"
+	notFoundPath := faceBase + "/.notfound"
+	ignorePath := faceBase + "/.ignorelist"
+	dictPath := outBase + "/.dict"
+
+	froms := map[string][md5.Size]byte{}
+
+	fs, err := ioutil.ReadDir(base)
+	if err != nil {
+		log.Fatal(err)
+	}
+	for _, fsi := range fs {
+		if !fsi.IsDir() {
+			continue
+		}
+
+		msgsBase := base + "/" + fsi.Name()
+		msgs, err := ioutil.ReadDir(msgsBase)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		for _, mi := range msgs {
+			if !mi.IsDir() {
+				continue
+			}
+			if from, err := ioutil.ReadFile(msgsBase + "/" + mi.Name() + "/from"); err != nil {
+				log.Fatal(err)
+			} else {
+				f := strings.ToLower(string(from))
+				froms[f] = md5.Sum([]byte(f))
+			}
+		}
+	}
+
+	if err = os.MkdirAll(outBase, 0700); err != nil {
+		log.Fatal(err)
+	}
+
+	var ignoreList []*regexp.Regexp
+	if s, err := ioutil.ReadFile(ignorePath); err == nil {
+		for _, v := range strings.Split(string(s), "\n") {
+			if v != "" {
+				if r, err := regexp.Compile(v); err != nil {
+					log.Fatalf("%s: %s", ignorePath, err)
+				} else {
+					ignoreList = append(ignoreList, r)
+				}
+			}
+		}
+	}
+
+	notFound := make(map[string]struct{})
+	if s, err := ioutil.ReadFile(notFoundPath); err == nil {
+		for _, v := range strings.Split(string(s), "\n") {
+			if v != "" {
+				notFound[v] = struct{}{}
+			}
+		}
+	}
+
+	dict := make(map[string]string)
+	if s, err := ioutil.ReadFile(dictPath); err == nil {
+		for _, v := range strings.Split(string(s), "\n") {
+			if v != "" {
+				parts := strings.Split(v, " ")
+				dict[parts[0]] = parts[1]
+			}
+		}
+	}
+
+	numTotal := len(froms)
+	i := 0
+	failed := 0
+	saved := 0
+	ignored := 0
+	progress := ""
+
+	for f, h := range froms {
+		hash := fmt.Sprintf("%x", h)
+		imagePath := outBase + "/" + hash
+
+		i++
+		clear := strings.Repeat("\x08", len(progress))
+		progress = fmt.Sprintf("%d/%d", i, numTotal)
+		fmt.Printf("%s%s", clear, progress)
+
+		var machineUser string
+		if parts := strings.Split(f, "@"); len(parts) != 2 {
+			failed++
+			continue
+		} else {
+			userParts := strings.Split(parts[0], "+")
+			machineUser = fmt.Sprintf("%s/%s", parts[1], userParts[0])
+
+			skip := false
+			for _, ignore := range ignoreList {
+				if ignore.MatchString(machineUser) {
+					ignored++
+					os.Remove(imagePath)
+					delete(dict, machineUser)
+					delete(notFound, machineUser)
+					skip = true
+				}
+			}
+			if skip {
+				continue
+			}
+			if _, ok := dict[machineUser]; ok {
+				continue
+			}
+			if _, ok := notFound[machineUser]; ok {
+				continue
+			}
+		}
+
+		url := fmt.Sprintf("http://gravatar.com/avatar/%s.jpg?s=48&d=404", hash)
+		if res, err := httpClient.Get(url); err != nil {
+			log.Fatal(err)
+		} else if res.StatusCode != http.StatusOK {
+			if res.StatusCode == http.StatusNotFound {
+				notFound[machineUser] = struct{}{}
+			}
+			res.Body.Close()
+			continue
+		} else {
+			b := new(bytes.Buffer)
+			b.ReadFrom(res.Body)
+			res.Body.Close()
+
+			data := new(bytes.Buffer)
+
+			cmd := exec.Command("/bin/jpg", "-c")
+			cmd.Stdin = bytes.NewReader(b.Bytes())
+			cmd.Stdout = data
+			if err = cmd.Run(); err != nil {
+				data.Reset()
+				cmd = exec.Command("/bin/png", "-c")
+				cmd.Stdin = bytes.NewReader(b.Bytes())
+				cmd.Stdout = data
+				err = cmd.Run()
+			}
+
+			if err != nil {
+				failed++
+			} else if err = ioutil.WriteFile(imagePath, data.Bytes(), 0644); err != nil {
+				log.Fatal(err)
+			} else {
+				dict[machineUser] = hash
+				saved++
+			}
+		}
+	}
+
+	if f, err := os.Create(notFoundPath); err != nil {
+		log.Fatal(err)
+	} else {
+		var sorted []string
+		for machineUser := range notFound {
+			sorted = append(sorted, machineUser)
+		}
+		sort.Strings(sorted)
+		for _, s := range sorted {
+			fmt.Fprintf(f, "%s\n", s)
+		}
+		f.Close()
+	}
+
+	if f, err := os.Create(dictPath); err != nil {
+		log.Fatal(err)
+	} else {
+		var sorted []string
+		for machineUser := range dict {
+			sorted = append(sorted, machineUser)
+		}
+		sort.Strings(sorted)
+		for _, s := range sorted {
+			fmt.Fprintf(f, "%s %s\n", s, dict[s])
+		}
+		f.Close()
+	}
+
+	fmt.Printf("%s", strings.Repeat("\x08", len(progress)))
+	fmt.Printf("%d addresses\n", numTotal)
+	fmt.Printf("%d faces added\n", saved)
+	fmt.Printf("%d failed to decode\n", failed)
+	fmt.Printf("%d ignored\n", ignored)
+}