package main import ( "bufio" "errors" "fmt" "io" "log" "os" "os/signal" "os/user" "runtime" "time" "github.com/akamensky/argparse" ) var ( distractorsFile string = "/etc/hyperfocus_distractors" predefDistractorsFile string = "/etc/hyperfocus_distractors_predefined" hostBackupFile string = "/etc/.backup_hosts" ) 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 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(src) if err != nil { return fmt.Errorf("could not open original file %s for copying: %s", src, err) } backupFile, err := os.OpenFile(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(hostBackupFile) { if err := copyFile(getHostFile(), hostBackupFile); 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 hostFile.Close() // For checking if /etc/hyperfocus_distractors or /etc/hyperfocus_distractors_predefined // exist. var ( distractorsFileWarn bool predefDistractorsFileWarn bool ) // If /etc/hyperfocus_distractors exists, take each host from it and append it // to /etc/hosts for blocking. Else set distractorsFileWarn to true. if fileExists(distractorsFile) { distractorsFileObj, err := os.Open(distractorsFile) if err != nil { log.Fatalln(err) } defer distractorsFileObj.Close() 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/hyperfocus_distractors_predefined exists, take each host from it and, // append it to /etc/hosts for blocking. Else set predefDistractorsFileWarn to true. if fileExists(predefDistractorsFile) { predefDistractorsFileObj, err := os.Open(predefDistractorsFile) if err != nil { log.Fatalln(err) } defer predefDistractorsFileObj.Close() 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(hostBackupFile, 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 a channel to receive os.Interrupt signal from user c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) // TODO: Read up on Goroutines and channels and figure how to 'pipe' the os.Signal // channel to the goroutine below. // Prepare channel to receive signal. Sleep for t minus seconds, // or break the countdown when interrupt signal is received from user, then // improve focus again. ticker := time.NewTicker(1 * time.Second) done := make(chan bool) go func() { for { select { case <-done: return case t := <-ticker.C: // TODO: Figure how to get the number of seconds as integers from t fmt.Println(t) case <-c: return } } }() time.Sleep(time.Duration(minutes*60) * time.Second) ticker.Stop() done <- true improveFocus() fmt.Println("Welcome back to hyperfocus mode 😊 ") } // Prints the current list of distractors to be blocked func listDistractors() { for _, v := range []string{distractorsFile, predefDistractorsFile} { userDistractorsFile, err := os.Open(v) if err != nil { fmt.Println(err) } defer userDistractorsFile.Close() scanner := bufio.NewScanner(userDistractorsFile) for scanner.Scan() { fmt.Println(scanner.Text()) } } } // Adds predefined distractors to .hyperfocus_distractors file func addPredefinedDistractors() { 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", } predefDistractorsFileObj, err := os.OpenFile(predefDistractorsFile, os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatalln(err) } defer predefDistractorsFileObj.Close() dataWriter := bufio.NewWriter(predefDistractorsFileObj) for _, v := range distractors { _, _ = dataWriter.WriteString(v + "\n") } dataWriter.Flush() } func main() { parser := argparse.NewParser("hf", "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/hyperfocus_distractors)") predefinedCmd := parser.NewCommand("predefined", "Add predefined set of distractors to block file") 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.") } }