hyperfocus/main.go
2021-12-29 20:57:15 -06:00

439 lines
11 KiB
Go

package main
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log"
"os"
"os/signal"
"os/user"
"path/filepath"
"runtime"
"time"
"github.com/akamensky/argparse"
)
var (
distractorsFile string = "/etc/hf_distractors"
predefDistractorsFile string = "/etc/hf_predef_distractors"
)
const resetDNSMsg string = `
Note that you may need to flush your system's DNS cache for changes to take effect.
In most cases this could be done by restarting the resolver daemon.
Windows systems: open PowerShell as administrator and run "ipconfig /flushdns"
macOS: open terminal and run "sudo dscacheutil -flushcache"
Linux with Systemd: open terminal and run "sudo systemctl restart systemd-resolved"
Other init systems or *BSD: You probably already know how to do this.`
// Check if <path> exists on the host filesystem
func fileExists(path string) bool {
_, err := os.Stat(path)
return !errors.Is(err, os.ErrNotExist)
}
// Copy src to dest
func copyFile(src string, dest string) error {
origFile, err := os.Open(filepath.Clean(src))
if err != nil {
return fmt.Errorf("could not open original file %s for copying: %s", src, err)
}
backupFile, err := os.OpenFile(filepath.Clean(dest), os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("could not open backup file %s for writing: %s", dest, err)
}
_, err = io.Copy(backupFile, origFile)
if err != nil {
return fmt.Errorf("could not copy %s to %s: %s", src, dest, err)
}
return nil
}
// Return system's host file as string
func getHostFile() string {
var hostFile string
switch runtime.GOOS {
case "windows":
hostFile = "/Windows/System32/drivers/etc/hosts"
default:
hostFile = "/etc/hosts"
}
return hostFile
}
// Check if running as super user
func isRoot() bool {
currUser, err := user.Current()
if err != nil {
log.Fatalf("[Super user check] Unable to get current user: %s\n", err)
}
return currUser.Username == "root"
}
// Disables access to websites that are defined as 'distractors'
func improveFocus() {
// Backup host file if a backup does not already exist
if !fileExists("/etc/.hosts.backup") {
if err := copyFile(getHostFile(), "/etc/.hosts.backup"); err != nil {
log.Fatalln(err)
}
}
// Open host file for writing/appending
hostFile, err := os.OpenFile(getHostFile(), os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
log.Fatalln(err)
}
defer func() {
if err := hostFile.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
// For checking if /etc/hf_distractors or /etc/hf_distractors_predefined
// exist.
var (
distractorsFileWarn bool
predefDistractorsFileWarn bool
)
// If /etc/hf_distractors exists, take each host from it and append it
// to /etc/hosts for blocking. Else set distractorsFileWarn to true.
if fileExists(filepath.Clean(distractorsFile)) {
distractorsFileObj, err := os.Open(filepath.Clean(distractorsFile))
if err != nil {
log.Fatalln(err)
}
defer func() {
if err := distractorsFileObj.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
scanner := bufio.NewScanner(distractorsFileObj)
for scanner.Scan() {
var hostLine string = fmt.Sprintf("127.0.0.1\t%s\n", scanner.Text())
if _, err := hostFile.WriteString(hostLine); err != nil {
log.Fatalln(err)
}
}
} else {
distractorsFileWarn = true
}
// If /etc/hf_predef_distractors exists, take each host from it and,
// append it to /etc/hosts for blocking. Else set predefDistractorsFileWarn to true.
if fileExists(filepath.Clean(predefDistractorsFile)) {
predefDistractorsFileObj, err := os.Open(filepath.Clean(predefDistractorsFile))
if err != nil {
log.Fatalln(err)
}
defer func() {
if err := predefDistractorsFileObj.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
scanner := bufio.NewScanner(predefDistractorsFileObj)
for scanner.Scan() {
var hostLine string = fmt.Sprintf("127.0.0.1\t%s\n", scanner.Text())
if _, err := hostFile.WriteString(hostLine); err != nil {
log.Fatalln(err)
}
}
} else {
predefDistractorsFileWarn = true
}
// Fail with warning if neither distractors files exist.
if distractorsFileWarn && predefDistractorsFileWarn {
log.Fatalln("Error: Please define a set of distractors in your distractors file, one per line.",
"Alternatively, you can use a predefined set by running `sudo hf predefined`.")
}
fmt.Println("Focus is now improved 😊")
}
// Enables access to websites that are defined as 'distractors'
func loseFocus() {
// Remove the current /etc/hosts file before restoring the backup.
if err := os.Remove(getHostFile()); err != nil {
log.Fatalln(err)
}
// Restore the backup of /etc/hosts
if err := copyFile("/etc/.hosts.backup", getHostFile()); err != nil {
log.Fatalln(err)
}
fmt.Println("Focus is now lost 🤪")
}
// Enables temporarily breaking concentration
func takeBreak(minutes int) {
fmt.Println()
fmt.Println("Your (probably) well-deserved break is commencing...")
loseFocus()
// Create notify context for os.Interrupt signal
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
// Create context with timeout for duration of minutes argument
ctx, cancel = context.WithTimeout(ctx, time.Duration(minutes*60)*time.Second)
defer cancel()
<-ctx.Done()
improveFocus()
}
// Prints the current list of distractors to be blocked
// If neither /etc/hf_distractors nor /etc/hf_predef_distractors exist,
// print error message to console but do not exit.
func listDistractors() {
// Open /etc/hf_distractors and store it as *os.File type
userDistractorsFile, err := os.Open(filepath.Clean(distractorsFile))
if err != nil {
fmt.Println(err)
}
defer func() {
if err := userDistractorsFile.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
// Initialize a new scanner, scan /etc/hf_distractors, and print to
// stdout line by line.
scanner := bufio.NewScanner(userDistractorsFile)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Open /etc/hf_predef_distractors and store it as *os.File type
predefDistractorsFile, err := os.Open(filepath.Clean(predefDistractorsFile))
if err != nil {
fmt.Println(err)
}
defer func() {
if err := predefDistractorsFile.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
// Initialize a new scanner, scan /etc/hf_predef_distractors, and print to
// stdout line by line.
scanner = bufio.NewScanner(predefDistractorsFile)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
// Adds predefined distractors to /etc/hf_predef_distractors file
func addPredefinedDistractors() {
// Slice of distractor domains
distractors := []string{
"anandtech.com",
"answerbag.com",
"arstechnica.com",
"awkwardfamilyphotos.com",
"bbc.com",
"befunky.com",
"break.com",
"buzzfeed.com",
"cheezburger.com",
"cnet.com",
"cnn.com",
"cracked.com",
"digg.com",
"digitaltrends.com",
"distrowatch.com",
"ebay.com",
"engadget.com",
"everypoet.com",
"facebook.com",
"failblog.com",
"fark.com",
"firstpost.com",
"flyertalk.com",
"fmylife.com",
"fox.com",
"freerice.com",
"gawker.com",
"geek.com",
"gizmag.com",
"gizmodo.com",
"gsmarena.com ",
"homestarrunner.com",
"hulu.com",
"imgur.com",
"imore.com",
"instagram.com",
"iwastesomuchtime.com",
"lifehacker.com",
"liveplasma.com",
"lunchtimers.com",
"mashable.com",
"mcsweeneys.net",
"medium.com",
"meetup.com",
"mono-1.com",
"myparentsjoinedfacebook.com",
"myspace.com",
"netflix.com",
"news.anandtech.com",
"news.arstechnica.com",
"news.buzzfeed.com",
"news.cheezburger.com",
"news.cnet.com",
"news.cracked.com",
"news.distrowatch.com",
"news.ebay.com",
"news.facebook.com",
"news.gizmodo.com",
"news.homestarrunner.com",
"news.imgur.com",
"news.lifehacker.com",
"news.meetup.com",
"news.myspace.com",
"news.reddit.com",
"news.sciencemag.org",
"news.slashdot.com",
"news.slashdot.org",
"news.stripgenerator.com",
"news.theverge.com",
"news.trulia.com",
"news.typepad.com",
"news.ycombinator.com",
"news.ycombinator.com",
"news.yelp.com",
"news.youtube.com",
"notalwaysright.com",
"omegle.com",
"opentable.com",
"passiveaggressivenotes.com",
"pcworld.com",
"petsinclothes.com",
"phoronix.com",
"pinterest.com",
"pogo.com",
"popurls.com",
"postsecret.com",
"realclearpolitics.com",
"realtor.com",
"reddit.com",
"redfin.com",
"sciencemag.org",
"sho.com",
"slashdot.com",
"slashdot.org",
"slickdeals.net",
"stripgenerator.com",
"techcrunch.com",
"technorati.com",
"techradar.com",
"techrepublic.com",
"textsfromlastnight.com",
"thenextweb.com",
"thepointsguy.com",
"theverge.com",
"tomshardware.com",
"trulia.com",
"tumblr.com",
"twitter.com",
"twitterfall.com",
"typepad.com",
"wearables.com",
"wired.com",
"woot.com",
"xkcd.com",
"yelp.com",
"youtube.com",
"zdnet.com",
"zillow.com",
}
// Open or create /etc/hf_predef_distractors and store it in *os.File type
predefDistractorsFileObj, err := os.OpenFile(filepath.Clean(predefDistractorsFile), os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatalln(err)
}
defer func() {
if err := predefDistractorsFileObj.Close(); err != nil {
log.Printf("Error closing file: %s\n", err)
}
}()
// Initialize a new writer, range loop over distractors slice, and write
// each distractor domain to /etc/hf_predef_distractors, line by line.
dataWriter := bufio.NewWriter(predefDistractorsFileObj)
for _, v := range distractors {
_, _ = dataWriter.WriteString(v + "\n")
}
if err := dataWriter.Flush(); err != nil {
log.Fatalln(err)
}
}
func main() {
parser := argparse.NewParser("go-hyperfocus", "Block distracting websites and hyperfocus on your work")
improveCmd := parser.NewCommand("improve", "Improve focus, block the distractors")
loseCmd := parser.NewCommand("lose", "Lose focus, unblock the distractors")
breakCmd := parser.NewCommand("break", "Take a break for a number of minutes")
minutesForBreak := breakCmd.Int("", "minutes", &argparse.Options{Help: "Number of minutes to break for."})
listCmd := parser.NewCommand("list", "List the distractors defined in the block file (/etc/hf_distractors)")
predefinedCmd := parser.NewCommand("predefined", "Add predefined set of distractors to /etc/hf_predef_distractors")
if isRoot() {
err := parser.Parse(os.Args)
if err != nil {
fmt.Print(parser.Usage(err))
}
if improveCmd.Happened() {
improveFocus()
} else if loseCmd.Happened() {
loseFocus()
} else if breakCmd.Happened() {
takeBreak(*minutesForBreak)
} else if listCmd.Happened() {
listDistractors()
} else if predefinedCmd.Happened() {
addPredefinedDistractors()
} else {
fmt.Println("Enter a subcommand; use --help or -h for details.")
}
} else {
fmt.Println("Super user not detected. This program requires root privileges; please re-run with sudo or become root.")
}
}