Skip to content

Commit 8bb83a1

Browse files
committed
initial commit
0 parents  commit 8bb83a1

File tree

7 files changed

+185
-0
lines changed

7 files changed

+185
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.idea/
2+
.vscode/
3+
dist/
4+
*.ico
5+
*.png
6+
*.syso
7+
*.log

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2022 Typomedia, Philipp Speck
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
build: tidy
2+
go build -ldflags "-s -w" -o dist/ .
3+
4+
run: tidy
5+
go run main.go
6+
7+
compile: tidy
8+
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w" -o dist/pruneimap-linux-arm .
9+
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o dist/pruneimap-linux-amd64 .
10+
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o dist/pruneimap-windows-amd64.exe .
11+
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o dist/pruneimap-macos-amd64 .
12+
GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w" -o dist/pruneimap-freebsd-amd64 .
13+
14+
tidy:
15+
go mod tidy
16+
17+
loc:
18+
go install github.com/boyter/scc/v3@latest
19+
scc --exclude-dir vendor --exclude-dir bin .

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# `pruneimap` - Remove empty imap folders
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/typomedia/gitti)](https://pkg.go.dev/github.com/typomedia/gitti)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/typomedia/pruneimap)](https://goreportcard.com/report/github.com/typomedia/pruneimap)
5+
6+
`pruneimap` is a command line tool to remove empty folders from an IMAP server.
7+
8+
## Usage
9+
10+
pruneimap [OPTIONS]
11+
12+
### Options
13+
14+
-s, --server string IMAP server address
15+
-p, --port int IMAP server port (default 993)
16+
-u, --user string IMAP user name
17+
-w, --pass string IMAP user password
18+
-d, --dry dry run
19+
-h, --help print help information
20+
21+
## Example
22+
23+
pruneimap --server imap.example.com --user [email protected] --pass secret --dry
24+
25+
## Help
26+
27+
pruneimap -h
28+
29+
## Build
30+
31+
make build
32+
33+
---
34+
Copyright © 2023 Typomedia Foundation. All rights reserved.

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module typo.media/pruneimap
2+
3+
go 1.19
4+
5+
require (
6+
github.com/emersion/go-imap/v2 v2.0.0-alpha.7
7+
github.com/spf13/pflag v1.0.5
8+
)
9+
10+
require (
11+
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect
12+
golang.org/x/text v0.8.0 // indirect
13+
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/emersion/go-imap/v2 v2.0.0-alpha.7 h1:CN25ax8kmTOLrhsxWIqlsDVvFYuIyaTYEHPOYgQ1S5A=
2+
github.com/emersion/go-imap/v2 v2.0.0-alpha.7/go.mod h1:NQQIs7aGbZC7CuvEp9yfidW2TCstC3rUIo4k8LbqxzA=
3+
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
4+
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
5+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
6+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7+
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
8+
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=

main.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/emersion/go-imap/v2/imapclient"
6+
"github.com/spf13/pflag"
7+
"io"
8+
"log"
9+
"os"
10+
"strings"
11+
)
12+
13+
func main() {
14+
15+
server := pflag.StringP("server", "s", "", "IMAP server address")
16+
port := pflag.StringP("port", "p", "993", "IMAP server port")
17+
username := pflag.StringP("user", "u", "", "IMAP user name")
18+
password := pflag.StringP("pass", "w", "", "IMAP user password")
19+
dryrun := pflag.BoolP("dry", "d", false, "dry run")
20+
pflag.Parse()
21+
22+
fmt.Print("pruneimap 1.0.0 <[email protected]>\n")
23+
24+
// write to log file
25+
logfile, err := os.OpenFile("pruneimap.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
26+
if err != nil {
27+
log.Fatal(err)
28+
}
29+
defer logfile.Close()
30+
31+
// write to stdout and log file
32+
log.SetOutput(io.MultiWriter(os.Stdout, logfile))
33+
34+
address := *server + ":" + *port
35+
client, err := imapclient.DialTLS(address, nil)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
defer client.Logout()
40+
41+
client.Login(*username, *password)
42+
43+
delete(client, "", "*", *dryrun)
44+
}
45+
46+
func delete(client *imapclient.Client, ref, pattern string, dryrun bool) {
47+
mailboxes, err := client.List(ref, pattern, nil).Collect()
48+
if err != nil {
49+
log.Fatal(err)
50+
}
51+
52+
for _, mailbox := range mailboxes {
53+
if ignore(mailbox.Mailbox) {
54+
log.Printf("IGNORED: %s\n", mailbox.Mailbox)
55+
continue
56+
}
57+
58+
mbox, err := client.Select(mailbox.Mailbox, nil).Wait()
59+
if err != nil {
60+
log.Fatal(err)
61+
}
62+
63+
// check if mailbox is empty
64+
if mbox.NumMessages == 0 {
65+
// check if mailbox has subfolders
66+
children, err := client.List(mailbox.Mailbox, "*", nil).Collect()
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
71+
// if mailbox has no subfolders, delete it
72+
if len(children) <= 1 { // 1 because of the current mailbox itself
73+
if !dryrun {
74+
err := client.Delete(mailbox.Mailbox).Wait()
75+
if err != nil {
76+
log.Printf("ERROR: %s\n", err)
77+
}
78+
log.Printf("DELETED: %s\n", mailbox.Mailbox)
79+
} else {
80+
log.Printf("DRYRUN: %s\n", mailbox.Mailbox)
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
func ignore(name string) bool {
88+
// List of mailbox names to ignore
89+
ignoreList := []string{"INBOX", "Sent", "Trash", "Drafts", "Junk", "Notes", "Spam"}
90+
91+
for _, ignored := range ignoreList {
92+
if strings.EqualFold(name, ignored) {
93+
return true
94+
}
95+
}
96+
return false
97+
}

0 commit comments

Comments
 (0)