History

first revision

Sigrid Haflínudóttir committed 6 years ago

Commit b3df5ea1 parent 56950592 files · +239 −0Browse files

Unified · Split

2 files
  1. README.md +24 −0
  2. main.go +215 −0
README.md+24−0
@@ -0,0 +1,24 @@
1+# mkfaces
2+
3+Some kind of Gravatar `face(6)` fetcher for Plan 9?
4+
5+Goes through `/mail/fs/*` and tries to create a face based on "from" of
6+each email. Stores information according to `face(6)`. It keeps the old
7+information intact so it should be considered safe to use on top of
8+whatever faces one already had in `/usr/$user/lib/face`.
9+
10+In addition it ignores "machine/user" if it matches any of regexps
11+stored in `/usr/$user/lib/face/.ignorelist`.
12+
13+Example of `.ignorelist`:
14+
15+```
16+meetup.com
17+slack.com
18+amazon.com
19+```
20+
21+Maybe I will have time to rewrite it in `rc` later, for now one can
22+build it this way: `GOOS=plan9 GOARCH=amd64 go build`.
23+
24+This sucks.
main.go+215−0
@@ -0,0 +1,215 @@
1+package main
2+
3+import (
4+ "bytes"
5+ "crypto/md5"
6+ "fmt"
7+ "io/ioutil"
8+ "log"
9+ "net/http"
10+ "os"
11+ "os/exec"
12+ "regexp"
13+ "sort"
14+ "strings"
15+)
16+
17+var (
18+ httpClient = &http.Client{}
19+ base = "/mail/fs"
20+)
21+
22+func main() {
23+ home, err := os.UserHomeDir()
24+ if err != nil {
25+ log.Fatal(err)
26+ }
27+ faceBase := home + "/lib/face"
28+ outBase := faceBase + "/48x48x8"
29+ notFoundPath := faceBase + "/.notfound"
30+ ignorePath := faceBase + "/.ignorelist"
31+ dictPath := outBase + "/.dict"
32+
33+ froms := map[string][md5.Size]byte{}
34+
35+ fs, err := ioutil.ReadDir(base)
36+ if err != nil {
37+ log.Fatal(err)
38+ }
39+ for _, fsi := range fs {
40+ if !fsi.IsDir() {
41+ continue
42+ }
43+
44+ msgsBase := base + "/" + fsi.Name()
45+ msgs, err := ioutil.ReadDir(msgsBase)
46+ if err != nil {
47+ log.Fatal(err)
48+ }
49+
50+ for _, mi := range msgs {
51+ if !mi.IsDir() {
52+ continue
53+ }
54+ if from, err := ioutil.ReadFile(msgsBase + "/" + mi.Name() + "/from"); err != nil {
55+ log.Fatal(err)
56+ } else {
57+ f := strings.ToLower(string(from))
58+ froms[f] = md5.Sum([]byte(f))
59+ }
60+ }
61+ }
62+
63+ if err = os.MkdirAll(outBase, 0700); err != nil {
64+ log.Fatal(err)
65+ }
66+
67+ var ignoreList []*regexp.Regexp
68+ if s, err := ioutil.ReadFile(ignorePath); err == nil {
69+ for _, v := range strings.Split(string(s), "\n") {
70+ if v != "" {
71+ if r, err := regexp.Compile(v); err != nil {
72+ log.Fatalf("%s: %s", ignorePath, err)
73+ } else {
74+ ignoreList = append(ignoreList, r)
75+ }
76+ }
77+ }
78+ }
79+
80+ notFound := make(map[string]struct{})
81+ if s, err := ioutil.ReadFile(notFoundPath); err == nil {
82+ for _, v := range strings.Split(string(s), "\n") {
83+ if v != "" {
84+ notFound[v] = struct{}{}
85+ }
86+ }
87+ }
88+
89+ dict := make(map[string]string)
90+ if s, err := ioutil.ReadFile(dictPath); err == nil {
91+ for _, v := range strings.Split(string(s), "\n") {
92+ if v != "" {
93+ parts := strings.Split(v, " ")
94+ dict[parts[0]] = parts[1]
95+ }
96+ }
97+ }
98+
99+ numTotal := len(froms)
100+ i := 0
101+ failed := 0
102+ saved := 0
103+ ignored := 0
104+ progress := ""
105+
106+ for f, h := range froms {
107+ hash := fmt.Sprintf("%x", h)
108+ imagePath := outBase + "/" + hash
109+
110+ i++
111+ clear := strings.Repeat("\x08", len(progress))
112+ progress = fmt.Sprintf("%d/%d", i, numTotal)
113+ fmt.Printf("%s%s", clear, progress)
114+
115+ var machineUser string
116+ if parts := strings.Split(f, "@"); len(parts) != 2 {
117+ failed++
118+ continue
119+ } else {
120+ userParts := strings.Split(parts[0], "+")
121+ machineUser = fmt.Sprintf("%s/%s", parts[1], userParts[0])
122+
123+ skip := false
124+ for _, ignore := range ignoreList {
125+ if ignore.MatchString(machineUser) {
126+ ignored++
127+ os.Remove(imagePath)
128+ delete(dict, machineUser)
129+ delete(notFound, machineUser)
130+ skip = true
131+ }
132+ }
133+ if skip {
134+ continue
135+ }
136+ if _, ok := dict[machineUser]; ok {
137+ continue
138+ }
139+ if _, ok := notFound[machineUser]; ok {
140+ continue
141+ }
142+ }
143+
144+ url := fmt.Sprintf("http://gravatar.com/avatar/%s.jpg?s=48&d=404", hash)
145+ if res, err := httpClient.Get(url); err != nil {
146+ log.Fatal(err)
147+ } else if res.StatusCode != http.StatusOK {
148+ if res.StatusCode == http.StatusNotFound {
149+ notFound[machineUser] = struct{}{}
150+ }
151+ res.Body.Close()
152+ continue
153+ } else {
154+ b := new(bytes.Buffer)
155+ b.ReadFrom(res.Body)
156+ res.Body.Close()
157+
158+ data := new(bytes.Buffer)
159+
160+ cmd := exec.Command("/bin/jpg", "-c")
161+ cmd.Stdin = bytes.NewReader(b.Bytes())
162+ cmd.Stdout = data
163+ if err = cmd.Run(); err != nil {
164+ data.Reset()
165+ cmd = exec.Command("/bin/png", "-c")
166+ cmd.Stdin = bytes.NewReader(b.Bytes())
167+ cmd.Stdout = data
168+ err = cmd.Run()
169+ }
170+
171+ if err != nil {
172+ failed++
173+ } else if err = ioutil.WriteFile(imagePath, data.Bytes(), 0644); err != nil {
174+ log.Fatal(err)
175+ } else {
176+ dict[machineUser] = hash
177+ saved++
178+ }
179+ }
180+ }
181+
182+ if f, err := os.Create(notFoundPath); err != nil {
183+ log.Fatal(err)
184+ } else {
185+ var sorted []string
186+ for machineUser := range notFound {
187+ sorted = append(sorted, machineUser)
188+ }
189+ sort.Strings(sorted)
190+ for _, s := range sorted {
191+ fmt.Fprintf(f, "%s\n", s)
192+ }
193+ f.Close()
194+ }
195+
196+ if f, err := os.Create(dictPath); err != nil {
197+ log.Fatal(err)
198+ } else {
199+ var sorted []string
200+ for machineUser := range dict {
201+ sorted = append(sorted, machineUser)
202+ }
203+ sort.Strings(sorted)
204+ for _, s := range sorted {
205+ fmt.Fprintf(f, "%s %s\n", s, dict[s])
206+ }
207+ f.Close()
208+ }
209+
210+ fmt.Printf("%s", strings.Repeat("\x08", len(progress)))
211+ fmt.Printf("%d addresses\n", numTotal)
212+ fmt.Printf("%d faces added\n", saved)
213+ fmt.Printf("%d failed to decode\n", failed)
214+ fmt.Printf("%d ignored\n", ignored)
215+}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t