From 725d174224dc5663bb8dabc098e48be9073d4f13 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 25 Mar 2018 22:07:58 +0200 Subject: [PATCH 01/23] Set default interpreter to base Python Script should be run by macOS base python version rather than any Python2.7 --- auto-selfcontrol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index ee70fff..e88f263 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/python import subprocess import os From 369ecdfe0d276dbb23f8897cc7ea732b6edfb5b7 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 25 Mar 2018 22:11:01 +0200 Subject: [PATCH 02/23] config.json is now in /usr/local/etc/auto-selfcontrol/ --- auto-selfcontrol.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index e88f263..3b33c43 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -248,7 +248,9 @@ def exit_with_error(message): if __name__ == "__main__": - __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) + config_dir = os.path.realpath('/usr/local/etc/auto-selfcontrol') + config_file = os.path.join(config_dir, 'config.json') + sys.excepthook = excepthook syslog.openlog("Auto-SelfControl") @@ -261,7 +263,7 @@ def exit_with_error(message): parser.add_option("-r", "--run", action="store_true", dest="run", default=False) (opts, args) = parser.parse_args() - config = load_config([os.path.join(__location__, "config.json")]) + config = load_config([config_file]) if opts.run: run(config) From a72461d100266fd3ef880a889a803440581e00b3 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 25 Mar 2018 22:24:57 +0200 Subject: [PATCH 03/23] Simple shell script to interact with Auto-SelfControl (config/activate/help) --- auto-selfcontrol | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 auto-selfcontrol diff --git a/auto-selfcontrol b/auto-selfcontrol new file mode 100755 index 0000000..974ce83 --- /dev/null +++ b/auto-selfcontrol @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Auto-SelfControl basic command-line interface + +CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" +b=$(tput bold) +n=$(tput sgr0) +HELP_TEXT="Auto-SelfControl (c) Andreas Grill +Small utility to schedule start and stop times of SelfControl. + +Usage: ${b}$(basename "$0") ${n} + +where: + ${b}config${n} Open the schedule configuration file in a text + editor to set up weekly parameters + ${b}activate${n} Activate the automatic start/stop of SelfControl + according to schedules defined in configuration + ${b}help${n} Show this help message + +More instructions at https://github.com/andreasgrill/auto-selfcontrol" + +if [[ $1 ]]; then + case "$1" in + config|edit|set|conf*) + if [[ ! -f $CONFIG_FILE ]]; then + curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE + echo "Downloaded sample configuration in $CONFIG_FILE" + fi + echo "Opening $CONFIG_FILE" + if [[ $EDITOR ]]; then + $EDITOR $CONFIG_FILE + else + open -t $CONFIG_FILE + fi + ;; + activate|install) + sudo /usr/bin/python auto-selfcontrol.py + exit 0 + ;; + -h|--help|help|*) + echo "$HELP_TEXT" + exit 0 + ;; + esac +else + echo "$HELP_TEXT" + exit 0 +fi From 90713df1008c57a9b9d54e6a73d1d4b43238a7c1 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 25 Mar 2018 22:33:24 +0200 Subject: [PATCH 04/23] Try to copy existing config.json in cwd to config directory --- auto-selfcontrol | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index 974ce83..acb1b7a 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -20,18 +20,30 @@ More instructions at https://github.com/andreasgrill/auto-selfcontrol" if [[ $1 ]]; then case "$1" in + # Edit configuration file config|edit|set|conf*) + # If no "config.json" in /usr/local/etc/auto-selfcontrol/ if [[ ! -f $CONFIG_FILE ]]; then - curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE - echo "Downloaded sample configuration in $CONFIG_FILE" + # If existing "config.json" in the cwd, copy it + if [[ -f config.json ]]; then + cp "config.json" $CONFIG_FILE + echo "Copied config.json from the current directory to $CONFIG_FILE" + # else download sample config from github repository + else + curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE + echo "Downloaded sample configuration in $CONFIG_FILE" + fi fi echo "Opening $CONFIG_FILE" + # Opening with default editor set as $EDITOR if [[ $EDITOR ]]; then $EDITOR $CONFIG_FILE + # Or with default GUI text editor (txt files > Open with...) else open -t $CONFIG_FILE fi ;; + # Install plist config activate|install) sudo /usr/bin/python auto-selfcontrol.py exit 0 From f583fa52259538ba37b29e14d1012780931debc8 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 25 Mar 2018 23:09:16 +0200 Subject: [PATCH 05/23] New installation (with brew) and usage recommendations --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e55a47a..d9c397b 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,56 @@ Auto-SelfControl helps you to create a weekly schedule for [SelfControl](http:// You can plan for every weekday if and when SelfControl should start and stop. -## Install -- [SelfControl](http://selfcontrolapp.com) is required and should be installed in the application directory (however, custom paths are also supported). -- Start SelfControl and backup your blacklist as it might get overridden by Auto-SelfControl. -- [Download Auto-SelfControl](../../archive/master.zip) and copy/extract it to a directory on your Mac (e.g. `~/auto-selfcontrol`). -- Edit the config.json (see [Configuration](#configuration) first). -- Open Terminal.app and navigate to the directory. (e.g. `cd ~/auto-selfcontrol`). -- Execute `/usr/bin/python setup.py install` to install the packages required to run Auto-SeltControl. -- Execute `sudo /usr/bin/python auto-selfcontrol.py` to install Auto-SelfControl with the block-schedule defined in [config.json](config.json). __Important:__ If you change [config.json](config.json) later, you have to call the installation command again or Auto-SelfControl might not start at the right time! +## Installation + +### With Homebrew (under construction) + +The easiest way to install Auto-SelfControl is with [Homebrew](https://brew.sh/). Install Auto-SelfControl by running the following command in the Terminal: + + brew install auto-selfcontrol + +If you already have [SelfControl](http://selfcontrolapp.com), start it and **backup your blacklist** as it might get overridden by Auto-SelfControl. + +If you do not have [SelfControl](http://selfcontrolapp.com) already installed on your system, you can install it with [Homebrew Cask](https://caskroom.github.io/): + + brew cask install selfcontrol + +### Manual installation + +Download this repository to a directory on your system (e.g., `~/auto-selfcontrol/`). + + chmod +x auto-selfcontrol + +Run from this specific repository + + ./auto-selfcontrol + +Or create a symlink in your `/usr/local/bin` folder to access it from anywhere. + +## Usage + +Edit the time configuration (see [Configuration](#configuration)) first: + + auto-selfcontrol config + +When your block-schedule in [config.json](config.json) is ready, activate it by running: + + auto-selfcontrol activate + +__Important:__ If you change [config.json](config.json) later, you have to call the `auto-selfcontrol activate` command again or Auto-SelfControl will not take the modifications into account! ## Uninstall -- Delete the installation directory of Auto-SelfControl -- Execute the following command in the Terminal.app: -``` -sudo rm /Library/LaunchDaemons/com.parrot-bytes.auto-selfcontrol.plist -``` + +To remove the application (if installed with Homebrew): + + brew uninstall auto-selfcontrol + +Or, manually, by removing the directory where you installed the files. + +You also need to remove the automatic schedule by executing the following command in the Terminal: + + sudo rm /Library/LaunchDaemons/com.parrot-bytes.auto-selfcontrol.plist ## Configuration The following listing shows an example config.json file that blocks every Monday from 9am to 5.30pm and on every Tuesday from 10am to 4pm: @@ -55,7 +89,7 @@ The following listing shows an example config.json file that blocks every Monday ] } ``` -- _username_ should be the Mac OS X username. +- _username_ should be the macOS username. - _selfcontrol-path_ is the absolute path to [SelfControl](http://selfcontrolapp.com). - _host-blacklist_ contains the list of sites that should get blacklisted as a string array. Please note that the blacklist in SelfControl might get overridden and should be __backed up__ before using Auto-SelfControl. - _block-schedules_ contains a list of schedules when SelfControl should be started. @@ -103,8 +137,8 @@ The following listing shows another example that blocks twitter and reddit every ### ImportError: No module named Foundation -If you've installed Python using HomeBrew, you'll need to run Auto-SelfControl with the original Python installation from OS X: +If you've installed another version of Python (e.g., using Homebrew), you'll need to run Auto-SelfControl with the original Python installation from macOS: sudo /usr/bin/python auto-selfcontrol.py - -There are also other options, including installing `pyobjc` on your brewed Python (`pip install pyobjc`). [See this thread for alternative solutions](https://stackoverflow.com/questions/1614648/importerror-no-module-named-foundation#1616361). + +There are also other options, including installing `pyobjc` on your own Python version (`pip install pyobjc`). [See this thread for alternative solutions](https://stackoverflow.com/questions/1614648/importerror-no-module-named-foundation#1616361). From 99bfb51ee5cc2c6baff31f7e1a4e060ae6f6957b Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Tue, 7 Aug 2018 23:52:59 +0200 Subject: [PATCH 06/23] Default config_dir = ~/.config/auto-selfcontrol --- auto-selfcontrol | 4 ++-- auto-selfcontrol.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index acb1b7a..7115be2 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Auto-SelfControl basic command-line interface -CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" +CONFIG_FILE="~/.config/auto-selfcontrol/config.json" b=$(tput bold) n=$(tput sgr0) HELP_TEXT="Auto-SelfControl (c) Andreas Grill @@ -22,7 +22,7 @@ if [[ $1 ]]; then case "$1" in # Edit configuration file config|edit|set|conf*) - # If no "config.json" in /usr/local/etc/auto-selfcontrol/ + # If no "config.json" in ~/.config/auto-selfcontrol/ if [[ ! -f $CONFIG_FILE ]]; then # If existing "config.json" in the cwd, copy it if [[ -f config.json ]]; then diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 3b33c43..287c909 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -248,7 +248,7 @@ def exit_with_error(message): if __name__ == "__main__": - config_dir = os.path.realpath('/usr/local/etc/auto-selfcontrol') + config_dir = os.path.realpath('~/.config/auto-selfcontrol') config_file = os.path.join(config_dir, 'config.json') sys.excepthook = excepthook From 98e8e5fea050044845e9cea13cf02402e7f76b76 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Tue, 21 Aug 2018 14:28:12 +0200 Subject: [PATCH 07/23] Corrected config_dir based on HOME directory --- auto-selfcontrol.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 287c909..467bf18 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -248,8 +248,9 @@ def exit_with_error(message): if __name__ == "__main__": - config_dir = os.path.realpath('~/.config/auto-selfcontrol') - config_file = os.path.join(config_dir, 'config.json') + home_dir = os.path.expanduser('~') + config_dir = os.path.realpath('.config/auto-selfcontrol') + config_file = os.path.join(home_dir, config_dir, 'config.json') sys.excepthook = excepthook From f1548fc9551f9635800a2f0564d5c29946927bc8 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Tue, 21 Aug 2018 16:07:07 +0200 Subject: [PATCH 08/23] Corrected config_dir + PEP8 improvements --- auto-selfcontrol.py | 66 +++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 467bf18..f72e6fe 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -13,26 +13,27 @@ def load_config(config_files): - """ loads json configuration files - the latter configs overwrite the previous configs """ + Load JSON configuration files. + The latter configs overwrite the previous configs. + """ config = dict() - for f in config_files: + for file in config_files: try: - with open(f, 'rt') as cfg: + with open(file, 'rt') as cfg: config.update(json.load(cfg)) - except ValueError as e: - exit_with_error("The json config file {configfile} is not correctly formatted." \ - "The following exception was raised:\n{exc}".format(configfile=f, exc=e)) + except ValueError as exception: + exit_with_error("The JSON config file {configfile} is not correctly formatted." + "The following exception was raised:\ + \n{exc}".format(configfile=file, exc=exception)) return config def run(config): - """ starts self-control with custom parameters, depending on the weekday and the config """ - + """Start self-control with custom parameters, depending on the weekday and the config.""" if check_if_running(config["username"]): syslog.syslog(syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) @@ -66,13 +67,13 @@ def run(config): def check_if_running(username): - """ checks if self-control is already running. """ + """Check if self-control is already running.""" defaults = get_selfcontrol_settings(username) return defaults.has_key("BlockStartedDate") and not NSDate.distantFuture().isEqualToDate_(defaults["BlockStartedDate"]) def is_schedule_active(schedule): - """ checks if we are right now in the provided schedule or not """ + """Check if we are right now in the provided schedule or not.""" currenttime = datetime.datetime.today() starttime = datetime.datetime(currenttime.year, currenttime.month, currenttime.day, schedule["start-hour"], schedule["start-minute"]) @@ -100,7 +101,7 @@ def is_schedule_active(schedule): def get_duration_minutes(endhour, endminute): - """ returns the minutes left until the schedule's end-hour and end-minute are reached """ + """Return the minutes left until the schedule's end-hour and end-minute are reached.""" currenttime = datetime.datetime.today() endtime = datetime.datetime(currenttime.year, currenttime.month, currenttime.day, endhour, endminute) d = endtime - currenttime @@ -108,12 +109,12 @@ def get_duration_minutes(endhour, endminute): def get_schedule_weekdays(schedule): - """ returns a list of weekdays the specified schedule is active """ + """Return a list of weekdays the specified schedule is active.""" return [schedule["weekday"]] if schedule.get("weekday", None) is not None else range(1, 8) def set_selfcontrol_setting(key, value, username): - """ sets a single default setting of SelfControl for the provied username """ + """Set a single default setting of SelfControl for the provided username.""" NSUserDefaults.resetStandardUserDefaults() originalUID = os.geteuid() os.seteuid(getpwnam(username).pw_uid) @@ -124,7 +125,7 @@ def set_selfcontrol_setting(key, value, username): def get_selfcontrol_settings(username): - """ returns all default settings of SelfControl for the provided username """ + """Return all default settings of SelfControl for the provided username.""" NSUserDefaults.resetStandardUserDefaults() originalUID = os.geteuid() os.seteuid(getpwnam(username).pw_uid) @@ -138,7 +139,7 @@ def get_selfcontrol_settings(username): def get_launchscript(config): - """ returns the string of the launchscript """ + """Return the string of the launchscript.""" return ''' @@ -161,11 +162,11 @@ def get_launchscript(config): def get_launchscript_startintervals(config): - """ returns the string of the launchscript start intervals """ - entries = list() + """Return the string of the launchscript start intervals.""" + # entries = list() for schedule in config["block-schedules"]: for weekday in get_schedule_weekdays(schedule): - yield (''' + yield ''' Weekday {weekday} Minute @@ -173,7 +174,7 @@ def get_launchscript_startintervals(config): Hour {starthour} - '''.format(weekday=weekday, startminute=schedule['start-minute'], starthour=schedule['start-hour'])) + '''.format(weekday=weekday, startminute=schedule['start-minute'], starthour=schedule['start-hour']) def install(config): @@ -233,7 +234,7 @@ def get_osx_usernames(): def excepthook(excType, excValue, tb): - """ this function is called whenever an exception is not caught """ + """ This function is called whenever an exception is not caught. """ err = "Uncaught exception:\n{}\n{}\n{}".format(str(excType), excValue, "".join(traceback.format_exception(excType, excValue, tb))) syslog.syslog(syslog.LOG_CRIT, err) @@ -248,31 +249,32 @@ def exit_with_error(message): if __name__ == "__main__": - home_dir = os.path.expanduser('~') - config_dir = os.path.realpath('.config/auto-selfcontrol') - config_file = os.path.join(home_dir, config_dir, 'config.json') + CONFIG_DIR = os.path.join(os.path.expanduser('~'), + '.config/auto-selfcontrol') + CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.json') sys.excepthook = excepthook syslog.openlog("Auto-SelfControl") if os.geteuid() != 0: - exit_with_error("Please make sure to run the script with elevated rights, such as:\nsudo python {file}".format( - file=os.path.realpath(__file__))) + exit_with_error("Please make sure to run the script with elevated \ + rights, such as:\nsudo python {file} \ + ".format(file=os.path.realpath(__file__))) parser = OptionParser() parser.add_option("-r", "--run", action="store_true", dest="run", default=False) (opts, args) = parser.parse_args() - config = load_config([config_file]) + CONFIG = load_config([CONFIG_FILE]) if opts.run: - run(config) + run(CONFIG) else: - check_config(config) - install(config) - if not check_if_running(config["username"]) and any(s for s in config["block-schedules"] if is_schedule_active(s)): + check_config(CONFIG) + install(CONFIG) + if not check_if_running(CONFIG["username"]) and any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") - run(config) + run(CONFIG) print("\n> SelfControl was started.\n") From f0f9b40a5ce3355050f3ccbd1185bdcf850711d5 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Tue, 4 Dec 2018 11:01:17 +0100 Subject: [PATCH 09/23] New /etc/ location for config.json + py style conventions --- auto-selfcontrol | 2 +- auto-selfcontrol.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index 7115be2..22ca9f7 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Auto-SelfControl basic command-line interface -CONFIG_FILE="~/.config/auto-selfcontrol/config.json" +CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" b=$(tput bold) n=$(tput sgr0) HELP_TEXT="Auto-SelfControl (c) Andreas Grill diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index f72e6fe..5cd4060 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -262,18 +262,19 @@ def exit_with_error(message): rights, such as:\nsudo python {file} \ ".format(file=os.path.realpath(__file__))) - parser = OptionParser() - parser.add_option("-r", "--run", action="store_true", + PARSER = OptionParser() + PARSER.add_option("-r", "--run", action="store_true", dest="run", default=False) - (opts, args) = parser.parse_args() + (OPTS, ARGS) = PARSER.parse_args() CONFIG = load_config([CONFIG_FILE]) - if opts.run: + if OPTS.run: run(CONFIG) else: check_config(CONFIG) install(CONFIG) - if not check_if_running(CONFIG["username"]) and any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): + if not check_if_running(CONFIG["username"]) and \ + any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") run(CONFIG) From ab5dae84715e94a40374bb88e6ebfa25ba3e88e7 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Wed, 5 Dec 2018 13:22:01 +0100 Subject: [PATCH 10/23] New /etc/ location for config.json --- auto-selfcontrol.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 5cd4060..29ebc06 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -249,8 +249,7 @@ def exit_with_error(message): if __name__ == "__main__": - CONFIG_DIR = os.path.join(os.path.expanduser('~'), - '.config/auto-selfcontrol') + CONFIG_DIR = os.path.join('/usr/local/etc/auto-selfcontrol') CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.json') sys.excepthook = excepthook From 158efd565f46bf94f5a5cb0729b8aa1e7b6c5d53 Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Wed, 8 May 2019 11:20:43 +0200 Subject: [PATCH 11/23] Homebrew Formula is in my personal tap --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d9c397b..d5b3335 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,12 @@ You can plan for every weekday if and when SelfControl should start and stop. ## Installation -### With Homebrew (under construction) +### With Homebrew The easiest way to install Auto-SelfControl is with [Homebrew](https://brew.sh/). Install Auto-SelfControl by running the following command in the Terminal: - brew install auto-selfcontrol + brew tap sbibauw/utils + brew install --HEAD auto-selfcontrol If you already have [SelfControl](http://selfcontrolapp.com), start it and **backup your blacklist** as it might get overridden by Auto-SelfControl. From 3a67a2f73ad592dedfd86a0c6f973baf07143b5a Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Thu, 16 May 2019 01:40:14 +0200 Subject: [PATCH 12/23] activate: full path to auto-selfcontrol.py --- auto-selfcontrol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index 22ca9f7..e60517e 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -45,7 +45,7 @@ if [[ $1 ]]; then ;; # Install plist config activate|install) - sudo /usr/bin/python auto-selfcontrol.py + sudo /usr/bin/python /usr/local/bin/auto-selfcontrol.py exit 0 ;; -h|--help|help|*) From b6eb65191aa934ceb5b41550458295053ee7c71c Mon Sep 17 00:00:00 2001 From: Serge Bibauw Date: Sun, 2 Jun 2019 14:10:40 +0200 Subject: [PATCH 13/23] Release to avoid needing --HEAD to install with brew --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5b3335..7969d13 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ You can plan for every weekday if and when SelfControl should start and stop. The easiest way to install Auto-SelfControl is with [Homebrew](https://brew.sh/). Install Auto-SelfControl by running the following command in the Terminal: brew tap sbibauw/utils - brew install --HEAD auto-selfcontrol + brew install auto-selfcontrol If you already have [SelfControl](http://selfcontrolapp.com), start it and **backup your blacklist** as it might get overridden by Auto-SelfControl. From f0555b1735bf717b7641197561cd03349fb9bfe9 Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 16:31:44 +0200 Subject: [PATCH 14/23] Change homebrew tap repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7969d13..aee68a8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ You can plan for every weekday if and when SelfControl should start and stop. The easiest way to install Auto-SelfControl is with [Homebrew](https://brew.sh/). Install Auto-SelfControl by running the following command in the Terminal: - brew tap sbibauw/utils + brew tap andreasgrill/utils brew install auto-selfcontrol If you already have [SelfControl](http://selfcontrolapp.com), start it and **backup your blacklist** as it might get overridden by Auto-SelfControl. From 945ee90967881ede06d44689b7c362f0cfd87110 Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 16:33:30 +0200 Subject: [PATCH 15/23] Add .gitignore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8887b3e --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ \ No newline at end of file From 1239068cdd00574a379c5366957e243b251dfdbf Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 16:35:25 +0200 Subject: [PATCH 16/23] Add vscode settings --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..18a43d7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.pythonPath": "/usr/bin/python", + "python.formatting.provider": "autopep8" +} \ No newline at end of file From 32eec001e0e5327b8761281b8009a6899678ff5e Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 16:49:19 +0200 Subject: [PATCH 17/23] Format code - Also remove copyright notice in shell script --- auto-selfcontrol | 2 +- auto-selfcontrol.py | 48 +++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index e60517e..85dd2cd 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -4,7 +4,7 @@ CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" b=$(tput bold) n=$(tput sgr0) -HELP_TEXT="Auto-SelfControl (c) Andreas Grill +HELP_TEXT="Auto-SelfControl Small utility to schedule start and stop times of SelfControl. Usage: ${b}$(basename "$0") ${n} diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 29ebc06..26ab787 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -35,35 +35,44 @@ def load_config(config_files): def run(config): """Start self-control with custom parameters, depending on the weekday and the config.""" if check_if_running(config["username"]): - syslog.syslog(syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") + syslog.syslog( + syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) try: - schedule = next(s for s in config["block-schedules"] if is_schedule_active(s)) + schedule = next( + s for s in config["block-schedules"] if is_schedule_active(s)) except StopIteration: - syslog.syslog(syslog.LOG_ALERT, "No schedule is active at the moment. Shutting down.") + syslog.syslog(syslog.LOG_ALERT, + "No schedule is active at the moment. Shutting down.") exit(0) - duration = get_duration_minutes(schedule["end-hour"], schedule["end-minute"]) + duration = get_duration_minutes( + schedule["end-hour"], schedule["end-minute"]) set_selfcontrol_setting("BlockDuration", duration, config["username"]) set_selfcontrol_setting("BlockAsWhitelist", 1 if schedule.get("block-as-whitelist", False) else 0, config["username"]) if schedule.get("host-blacklist", None) is not None: - set_selfcontrol_setting("HostBlacklist", schedule["host-blacklist"], config["username"]) + set_selfcontrol_setting( + "HostBlacklist", schedule["host-blacklist"], config["username"]) elif config.get("host-blacklist", None) is not None: - set_selfcontrol_setting("HostBlacklist", config["host-blacklist"], config["username"]) + set_selfcontrol_setting( + "HostBlacklist", config["host-blacklist"], config["username"]) # In legacy mode manually set the BlockStartedDate, this should not be required anymore in future versions # of SelfControl. if config.get("legacy-mode", True): - set_selfcontrol_setting("BlockStartedDate", NSDate.date(), config["username"]) + set_selfcontrol_setting( + "BlockStartedDate", NSDate.date(), config["username"]) # Start SelfControl - os.system("{path}/Contents/MacOS/org.eyebeam.SelfControl {userId} --install".format(path=config["selfcontrol-path"], userId=str(getpwnam(config["username"]).pw_uid))) + os.system("{path}/Contents/MacOS/org.eyebeam.SelfControl {userId} --install".format( + path=config["selfcontrol-path"], userId=str(getpwnam(config["username"]).pw_uid))) - syslog.syslog(syslog.LOG_ALERT, "SelfControl started for {min} minute(s).".format(min=duration)) + syslog.syslog(syslog.LOG_ALERT, + "SelfControl started for {min} minute(s).".format(min=duration)) def check_if_running(username): @@ -103,7 +112,8 @@ def is_schedule_active(schedule): def get_duration_minutes(endhour, endminute): """Return the minutes left until the schedule's end-hour and end-minute are reached.""" currenttime = datetime.datetime.today() - endtime = datetime.datetime(currenttime.year, currenttime.month, currenttime.day, endhour, endminute) + endtime = datetime.datetime( + currenttime.year, currenttime.month, currenttime.day, endhour, endminute) d = endtime - currenttime return int(round(d.seconds / 60.0)) @@ -163,7 +173,6 @@ def get_launchscript(config): def get_launchscript_startintervals(config): """Return the string of the launchscript start intervals.""" - # entries = list() for schedule in config["block-schedules"]: for weekday in get_schedule_weekdays(schedule): yield ''' @@ -205,17 +214,18 @@ def check_config(config): exit_with_error("No username specified in config.") if config["username"] not in get_osx_usernames(): exit_with_error( - "Username '{username}' unknown.\nPlease use your OSX username instead.\n" \ - "If you have trouble finding it, just enter the command 'whoami'\n" \ - "in your terminal.".format( - username=config["username"])) + "Username '{username}' unknown.\nPlease use your OSX username instead.\n" + "If you have trouble finding it, just enter the command 'whoami'\n" + "in your terminal.".format( + username=config["username"])) if not config.has_key("selfcontrol-path"): - exit_with_error("The setting 'selfcontrol-path' is required and must point to the location of SelfControl.") + exit_with_error( + "The setting 'selfcontrol-path' is required and must point to the location of SelfControl.") if not os.path.exists(config["selfcontrol-path"]): exit_with_error( - "The setting 'selfcontrol-path' does not point to the correct location of SelfControl. " \ - "Please make sure to use an absolute path and include the '.app' extension, " \ - "e.g. /Applications/SelfControl.app") + "The setting 'selfcontrol-path' does not point to the correct location of SelfControl. " + "Please make sure to use an absolute path and include the '.app' extension, " + "e.g. /Applications/SelfControl.app") if not config.has_key("block-schedules"): exit_with_error("The setting 'block-schedules' is required.") if len(config["block-schedules"]) == 0: From 3f0d7ca301bc52774d914eb2261ee97d95cb0c5c Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 19:34:18 +0200 Subject: [PATCH 18/23] Support config.json from same script path too - Manually create dir for new config location if it's not already existing. - Print a warning if multiple config.json files were found --- auto-selfcontrol | 8 +++--- auto-selfcontrol.py | 65 ++++++++++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/auto-selfcontrol b/auto-selfcontrol index 85dd2cd..21875e1 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Auto-SelfControl basic command-line interface -CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" +CONFIG_DIR="/usr/local/etc/auto-selfcontrol" +CONFIG_FILE="$CONFIG_DIR/config.json" b=$(tput bold) n=$(tput sgr0) HELP_TEXT="Auto-SelfControl @@ -22,12 +23,13 @@ if [[ $1 ]]; then case "$1" in # Edit configuration file config|edit|set|conf*) - # If no "config.json" in ~/.config/auto-selfcontrol/ + # If no "config.json" found if [[ ! -f $CONFIG_FILE ]]; then + mkdir -p $CONFIG_DIR # If existing "config.json" in the cwd, copy it if [[ -f config.json ]]; then cp "config.json" $CONFIG_FILE - echo "Copied config.json from the current directory to $CONFIG_FILE" + echo "Copied config.json from the current directory to $CONFIG_DIR" # else download sample config from github repository else curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 26ab787..77288bb 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -12,28 +12,24 @@ from optparse import OptionParser -def load_config(config_files): - """ - Load JSON configuration files. - - The latter configs overwrite the previous configs. - """ +def load_config(path): + """Load a JSON configuration file""" config = dict() - for file in config_files: - try: - with open(file, 'rt') as cfg: - config.update(json.load(cfg)) - except ValueError as exception: - exit_with_error("The JSON config file {configfile} is not correctly formatted." - "The following exception was raised:\ - \n{exc}".format(configfile=file, exc=exception)) + try: + with open(path, 'rt') as cfg: + config.update(json.load(cfg)) + except ValueError as exception: + exit_with_error("The JSON config file {configfile} is not correctly formatted." + "The following exception was raised:\ + \n{exc}".format(configfile=path, exc=exception)) return config -def run(config): - """Start self-control with custom parameters, depending on the weekday and the config.""" +def runApiV2(config): + """Start SelfControl (< 3.0) with custom parameters, depending on the weekday and the config.""" + if check_if_running(config["username"]): syslog.syslog( syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") @@ -68,8 +64,7 @@ def run(config): "BlockStartedDate", NSDate.date(), config["username"]) # Start SelfControl - os.system("{path}/Contents/MacOS/org.eyebeam.SelfControl {userId} --install".format( - path=config["selfcontrol-path"], userId=str(getpwnam(config["username"]).pw_uid))) + execSelfControl(config, ["--install"]) syslog.syslog(syslog.LOG_ALERT, "SelfControl started for {min} minute(s).".format(min=duration)) @@ -186,6 +181,13 @@ def get_launchscript_startintervals(config): '''.format(weekday=weekday, startminute=schedule['start-minute'], starthour=schedule['start-hour']) +def execSelfControl(config, arguments): + user_id = str(getpwnam(config["username"]).pw_uid) + output = subprocess.check_output( + ["{path}/Contents/MacOS/org.eyebeam.SelfControl".format(path=config["selfcontrol-path"]), user_id] + arguments) + return output + + def install(config): """ installs auto-selfcontrol """ print("> Start installation of Auto-SelfControl") @@ -259,9 +261,10 @@ def exit_with_error(message): if __name__ == "__main__": - CONFIG_DIR = os.path.join('/usr/local/etc/auto-selfcontrol') - CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.json') - + CONFIG_DIRS = [ + os.path.join('/usr/local/etc/auto-selfcontrol'), + os.path.dirname(os.path.realpath(__file__)) + ] sys.excepthook = excepthook syslog.openlog("Auto-SelfControl") @@ -275,16 +278,28 @@ def exit_with_error(message): PARSER.add_option("-r", "--run", action="store_true", dest="run", default=False) (OPTS, ARGS) = PARSER.parse_args() - CONFIG = load_config([CONFIG_FILE]) + + CONFIG_FILES = filter(lambda p: os.path.exists(p), map( + lambda d: os.path.join(d, 'config.json'), CONFIG_DIRS)) + + if len(CONFIG_FILES) is 0: + exit_with_error( + "There was no config file found, please create a config file.") + + if len(CONFIG_FILES) > 1: + print("> Multiple config files were found, use config file with path: {path}".format( + path=CONFIG_FILES[0])) + + CONFIG = load_config(CONFIG_FILES[0]) if OPTS.run: - run(CONFIG) + runApiV2(CONFIG) else: check_config(CONFIG) install(CONFIG) if not check_if_running(CONFIG["username"]) and \ - any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): + any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") - run(CONFIG) + runApiV2(CONFIG) print("\n> SelfControl was started.\n") From 9f3a83a9586693a8e63ebfa94b1528710b49d58f Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 10 May 2020 21:38:43 +0200 Subject: [PATCH 19/23] Add support for multiple API versions - Add detection for SelfControl's supported API version. - Start adding support for SelfControl 3.x (WIP) --- auto-selfcontrol.py | 78 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 77288bb..78a71b9 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -7,11 +7,17 @@ import syslog import traceback import sys +import re from Foundation import NSUserDefaults, CFPreferencesSetAppValue, CFPreferencesAppSynchronize, NSDate from pwd import getpwnam from optparse import OptionParser +class Api: + V2 = 2 + V3 = 3 + + def load_config(path): """Load a JSON configuration file""" config = dict() @@ -27,10 +33,28 @@ def load_config(path): return config +def detect_api(config): + """Return the supported API version of the SelfControl""" + try: + output = execSelfControl(config, ["--version"]) + m = re.search( + get_selfcontrol_out_pattern(r'(\d+)\.\d+\.\d+'), output, re.MULTILINE) + if m is None: + exit_with_error("Could not parse SelfControl version response!") + if m and int(m.groups()[0]) >= Api.V3: + return Api.V3 + + exit_with_error("Unexpected version returned from SelfControl '{version}'".format( + version=m.groups()[0])) + except: + # SelfControl < 3.0.0 does not support the --version argument + return Api.V2 + + def runApiV2(config): - """Start SelfControl (< 3.0) with custom parameters, depending on the weekday and the config.""" + """Start SelfControl (< 3.0) with custom parameters, depending on the weekday and the config""" - if check_if_running(config["username"]): + if check_if_running(Api.V2, config): syslog.syslog( syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) @@ -70,10 +94,36 @@ def runApiV2(config): "SelfControl started for {min} minute(s).".format(min=duration)) -def check_if_running(username): - """Check if self-control is already running.""" - defaults = get_selfcontrol_settings(username) - return defaults.has_key("BlockStartedDate") and not NSDate.distantFuture().isEqualToDate_(defaults["BlockStartedDate"]) +def runApiV3(config): + """Start SelfControl with custom parameters, depending on the weekday and the config""" + + if check_if_running(Api.V3, config): + syslog.syslog( + syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") + exit(2) + + +def get_selfcontrol_out_pattern(content_pattern): + """Returns a RegEx pattern that matches SelfControl's output with the provided content_pattern""" + return r'^.*org\.eyebeam\.SelfControl[^ ]+\s*' + content_pattern + r'\s*$' + + +def check_if_running(api, config): + """Check if SelfControl is already running.""" + if api is Api.V2: + username = config["username"] + defaults = get_selfcontrol_settings(username) + return defaults.has_key("BlockStartedDate") and not NSDate.distantFuture().isEqualToDate_(defaults["BlockStartedDate"]) + elif api is Api.V3: + output = execSelfControl(config, ["--is-running"]) + m = re.search( + get_selfcontrol_out_pattern(r'(NO|YES)'), output, re.MULTILINE) + if m is None: + exit_with_error("Could not detect if SelfControl is running.") + return m.groups()[0] != 'NO' + else: + raise Exception( + "Unknown API version {version} passed.".format(version=api)) def is_schedule_active(schedule): @@ -184,7 +234,10 @@ def get_launchscript_startintervals(config): def execSelfControl(config, arguments): user_id = str(getpwnam(config["username"]).pw_uid) output = subprocess.check_output( - ["{path}/Contents/MacOS/org.eyebeam.SelfControl".format(path=config["selfcontrol-path"]), user_id] + arguments) + ["{path}/Contents/MacOS/org.eyebeam.SelfControl".format( + path=config["selfcontrol-path"]), user_id] + arguments, + stderr=subprocess.STDOUT + ) return output @@ -292,12 +345,19 @@ def exit_with_error(message): CONFIG = load_config(CONFIG_FILES[0]) + api = detect_api(CONFIG) + print("Detected API v{version}".format(version=api)) + if OPTS.run: - runApiV2(CONFIG) + if api is Api.V2: + runApiV2(CONFIG) + elif api is Api.V3: + runApiV3(CONFIG) else: check_config(CONFIG) install(CONFIG) - if not check_if_running(CONFIG["username"]) and \ + if api is Api.V3 and \ + not check_if_running(api, CONFIG) and \ any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") From 3833caa7e280a662941189bbdb6a9bd18fd67e61 Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sat, 16 May 2020 19:40:40 +0200 Subject: [PATCH 20/23] Add support for SelfControl 3 - Use workaround to create ISO8601 block end string for Python2 - Create blocklist plist file for SelfControl 3 --- auto-selfcontrol.py | 83 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 78a71b9..13c4683 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -3,7 +3,9 @@ import subprocess import os import json -import datetime +import time +from datetime import datetime +import plistlib import syslog import traceback import sys @@ -12,6 +14,8 @@ from pwd import getpwnam from optparse import OptionParser +SETTINGS_DIR = '/usr/local/etc/auto-selfcontrol' + class Api: V2 = 2 @@ -51,10 +55,11 @@ def detect_api(config): return Api.V2 -def runApiV2(config): +def run_api_v2(config): """Start SelfControl (< 3.0) with custom parameters, depending on the weekday and the config""" if check_if_running(Api.V2, config): + print "SelfControl is already running, exit" syslog.syslog( syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) @@ -63,6 +68,7 @@ def runApiV2(config): schedule = next( s for s in config["block-schedules"] if is_schedule_active(s)) except StopIteration: + print("No Schedule is active at the moment.") syslog.syslog(syslog.LOG_ALERT, "No schedule is active at the moment. Shutting down.") exit(0) @@ -94,14 +100,35 @@ def runApiV2(config): "SelfControl started for {min} minute(s).".format(min=duration)) -def runApiV3(config): +def run_api_v3(config, settings_dir): """Start SelfControl with custom parameters, depending on the weekday and the config""" if check_if_running(Api.V3, config): + print "SelfControl is already running, exit" syslog.syslog( syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) + try: + schedule = next( + s for s in config["block-schedules"] if is_schedule_active(s)) + except StopIteration: + print("No Schedule is active at the moment.") + syslog.syslog(syslog.LOG_ALERT, + "No schedule is active at the moment. Shutting down.") + exit(0) + + block_end_date = get_end_date_of_schedule(schedule) + blocklist_path = "{settings}/blocklist".format(settings=settings_dir) + + update_blocklist(blocklist_path, config, schedule) + + # Start SelfControl + execSelfControl(config, ["--install", blocklist_path, block_end_date]) + + syslog.syslog(syslog.LOG_ALERT, + "SelfControl started until {end} minute(s).".format(end=block_end_date)) + def get_selfcontrol_out_pattern(content_pattern): """Returns a RegEx pattern that matches SelfControl's output with the provided content_pattern""" @@ -128,11 +155,11 @@ def check_if_running(api, config): def is_schedule_active(schedule): """Check if we are right now in the provided schedule or not.""" - currenttime = datetime.datetime.today() - starttime = datetime.datetime(currenttime.year, currenttime.month, currenttime.day, schedule["start-hour"], - schedule["start-minute"]) - endtime = datetime.datetime(currenttime.year, currenttime.month, currenttime.day, schedule["end-hour"], - schedule["end-minute"]) + currenttime = datetime.today() + starttime = datetime(currenttime.year, currenttime.month, currenttime.day, schedule["start-hour"], + schedule["start-minute"]) + endtime = datetime(currenttime.year, currenttime.month, currenttime.day, schedule["end-hour"], + schedule["end-minute"]) d = endtime - starttime for weekday in get_schedule_weekdays(schedule): @@ -156,13 +183,28 @@ def is_schedule_active(schedule): def get_duration_minutes(endhour, endminute): """Return the minutes left until the schedule's end-hour and end-minute are reached.""" - currenttime = datetime.datetime.today() - endtime = datetime.datetime( + currenttime = datetime.today() + endtime = datetime( currenttime.year, currenttime.month, currenttime.day, endhour, endminute) d = endtime - currenttime return int(round(d.seconds / 60.0)) +def get_end_date_of_schedule(schedule): + """Return the end date of the provided schedule in ISO 8601 format""" + currenttime = datetime.today() + endtime = datetime( + currenttime.year, currenttime.month, currenttime.day, schedule['end-hour'], schedule['end-minute']) + # manually create ISO8601 string because of tz issues with Python2 + ts = time.time() + utc_offset = ((datetime.fromtimestamp( + ts) - datetime.utcfromtimestamp(ts)).total_seconds()) / 3600 + offset = str(int(utc_offset * 100)).zfill(4) + sign = "+" if utc_offset >= 0 else "-" + + return endtime.strftime("%Y.%m.%dT%H:%M:%S{sign}{offset}".format(sign=sign, offset=offset)) + + def get_schedule_weekdays(schedule): """Return a list of weekdays the specified schedule is active.""" return [schedule["weekday"]] if schedule.get("weekday", None) is not None else range(1, 8) @@ -293,6 +335,16 @@ def check_config(config): syslog.syslog(syslog.LOG_WARNING, msg) +def update_blocklist(blocklist_path, config, schedule): + """Save the blocklist with the current configuration""" + plist = { + "HostBlacklist": config["host-blacklist"], + "BlockAsWhitelist": schedule.get("block-as-whitelist", False) + } + with open(blocklist_path, 'wb') as fp: + plistlib.writePlist(plist, fp) + + def get_osx_usernames(): output = subprocess.check_output(["dscl", ".", "list", "/users"]) return [s.strip() for s in output.splitlines()] @@ -315,7 +367,7 @@ def exit_with_error(message): if __name__ == "__main__": CONFIG_DIRS = [ - os.path.join('/usr/local/etc/auto-selfcontrol'), + os.path.join(SETTINGS_DIR), os.path.dirname(os.path.realpath(__file__)) ] sys.excepthook = excepthook @@ -348,11 +400,14 @@ def exit_with_error(message): api = detect_api(CONFIG) print("Detected API v{version}".format(version=api)) + if not os.path.exists(SETTINGS_DIR): + os.makedirs(SETTINGS_DIR) + if OPTS.run: if api is Api.V2: - runApiV2(CONFIG) + run_api_v2(CONFIG) elif api is Api.V3: - runApiV3(CONFIG) + run_api_v3(CONFIG, SETTINGS_DIR) else: check_config(CONFIG) install(CONFIG) @@ -361,5 +416,5 @@ def exit_with_error(message): any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") - runApiV2(CONFIG) + run_api_v2(CONFIG) print("\n> SelfControl was started.\n") From e6313c0480c1198837e9b954942f495cf6995d79 Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sat, 16 May 2020 20:53:00 +0200 Subject: [PATCH 21/23] Update README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index aee68a8..6f84eac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -# Important: SelfControl 3.0 Update -Auto-SelfControl will support SelfControl 3.0 in the coming release, which will be hopefully out in the next few days. - # Auto-SelfControl Small utility to schedule start and stop times of [SelfControl](http://selfcontrolapp.com). From ee6ec83cf5557275073c7534e25d19ae918d865e Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 17 May 2020 15:47:17 +0200 Subject: [PATCH 22/23] Store config file in central location after installing Copy the config file to a central location `/usr/local/etc/auto-selfcontrol` to normalize homebrew and manual installations. --- README.md | 8 ++++- auto-selfcontrol | 26 +++++++-------- auto-selfcontrol.py | 79 ++++++++++++++++++++++++++++----------------- 3 files changed, 70 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 6f84eac..e49ce79 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ Run from this specific repository ./auto-selfcontrol -Or create a symlink in your `/usr/local/bin` folder to access it from anywhere. +Optionally create a symlink in your `/usr/local/bin` folder to access it from anywhere: + + sudo ln -s ./auto-selfcontrol /usr/local/bin/auto-selfcontrol ## Usage @@ -55,6 +57,9 @@ To remove the application (if installed with Homebrew): Or, manually, by removing the directory where you installed the files. + sudo unlink /usr/local/bin/auto-selfcontrol + rm -rf ~/auto-selfcontrol + You also need to remove the automatic schedule by executing the following command in the Terminal: sudo rm /Library/LaunchDaemons/com.parrot-bytes.auto-selfcontrol.plist @@ -140,3 +145,4 @@ If you've installed another version of Python (e.g., using Homebrew), you'll nee sudo /usr/bin/python auto-selfcontrol.py There are also other options, including installing `pyobjc` on your own Python version (`pip install pyobjc`). [See this thread for alternative solutions](https://stackoverflow.com/questions/1614648/importerror-no-module-named-foundation#1616361). + diff --git a/auto-selfcontrol b/auto-selfcontrol index 21875e1..be76513 100755 --- a/auto-selfcontrol +++ b/auto-selfcontrol @@ -1,8 +1,16 @@ #!/usr/bin/env bash # Auto-SelfControl basic command-line interface -CONFIG_DIR="/usr/local/etc/auto-selfcontrol" -CONFIG_FILE="$CONFIG_DIR/config.json" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# Check if auto-selfcontrol was installed through brew and set the config.json location accordingly +if [[ $DIR == /usr/local/* ]]; then + mkdir -p /usr/local/etc/auto-selfcontrol || true + CONFIG_FILE="/usr/local/etc/auto-selfcontrol/config.json" +else + CONFIG_FILE="$DIR/config.json" +fi + b=$(tput bold) n=$(tput sgr0) HELP_TEXT="Auto-SelfControl @@ -25,16 +33,8 @@ if [[ $1 ]]; then config|edit|set|conf*) # If no "config.json" found if [[ ! -f $CONFIG_FILE ]]; then - mkdir -p $CONFIG_DIR - # If existing "config.json" in the cwd, copy it - if [[ -f config.json ]]; then - cp "config.json" $CONFIG_FILE - echo "Copied config.json from the current directory to $CONFIG_DIR" - # else download sample config from github repository - else - curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE - echo "Downloaded sample configuration in $CONFIG_FILE" - fi + curl -L -s "https://raw.githubusercontent.com/andreasgrill/auto-selfcontrol/master/config.json" -o $CONFIG_FILE + echo "Downloaded sample configuration in $CONFIG_FILE" fi echo "Opening $CONFIG_FILE" # Opening with default editor set as $EDITOR @@ -47,7 +47,7 @@ if [[ $1 ]]; then ;; # Install plist config activate|install) - sudo /usr/bin/python /usr/local/bin/auto-selfcontrol.py + sudo /usr/bin/python $DIR/auto-selfcontrol.py exit 0 ;; -h|--help|help|*) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 13c4683..0be3b66 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -37,6 +37,23 @@ def load_config(path): return config +def find_config(): + """Looks for the config.json and returns its path""" + local_config_file = "{path}/config.json".format( + path=os.path.dirname(os.path.realpath(__file__))) + global_config_file = "{path}/config.json".format( + path=SETTINGS_DIR) + + if os.path.exists(local_config_file): + return local_config_file + + if os.path.exists(global_config_file): + return global_config_file + + exit_with_error( + "There was no config file found, please create a config file.") + + def detect_api(config): """Return the supported API version of the SelfControl""" try: @@ -55,6 +72,23 @@ def detect_api(config): return Api.V2 +def run(settings_dir): + """Load config and start SelfControl""" + run_config = "{path}/run_config.json".format(path=settings_dir) + if not os.path.exists(run_config): + exit_with_error( + "Run config file could not be found in installation location, please make sure that you have Auto-SelfControl installed with '--install'") + + api = detect_api(CONFIG) + print("Detected API v{version}".format(version=api)) + + config = load_config(run_config) + if api is Api.V2: + run_api_v2(config) + elif api is Api.V3: + run_api_v3(config, settings_dir) + + def run_api_v2(config): """Start SelfControl (< 3.0) with custom parameters, depending on the weekday and the config""" @@ -283,7 +317,7 @@ def execSelfControl(config, arguments): return output -def install(config): +def install(config, settings_dir): """ installs auto-selfcontrol """ print("> Start installation of Auto-SelfControl") @@ -302,6 +336,13 @@ def install(config): subprocess.call(["launchctl", "load", "-w", launchplist_path]) + print("> Save run configuration") + if not os.path.exists(settings_dir): + os.makedirs(settings_dir) + + with open("{dir}/run_config.json".format(dir=settings_dir), 'w') as fp: + fp.write(json.dumps(config)) + print("> Installed\n") @@ -366,10 +407,6 @@ def exit_with_error(message): if __name__ == "__main__": - CONFIG_DIRS = [ - os.path.join(SETTINGS_DIR), - os.path.dirname(os.path.realpath(__file__)) - ] sys.excepthook = excepthook syslog.openlog("Auto-SelfControl") @@ -384,33 +421,17 @@ def exit_with_error(message): dest="run", default=False) (OPTS, ARGS) = PARSER.parse_args() - CONFIG_FILES = filter(lambda p: os.path.exists(p), map( - lambda d: os.path.join(d, 'config.json'), CONFIG_DIRS)) - - if len(CONFIG_FILES) is 0: - exit_with_error( - "There was no config file found, please create a config file.") - - if len(CONFIG_FILES) > 1: - print("> Multiple config files were found, use config file with path: {path}".format( - path=CONFIG_FILES[0])) - - CONFIG = load_config(CONFIG_FILES[0]) - - api = detect_api(CONFIG) - print("Detected API v{version}".format(version=api)) - - if not os.path.exists(SETTINGS_DIR): - os.makedirs(SETTINGS_DIR) - if OPTS.run: - if api is Api.V2: - run_api_v2(CONFIG) - elif api is Api.V3: - run_api_v3(CONFIG, SETTINGS_DIR) + run(SETTINGS_DIR) else: + CONFIG_FILE = find_config() + CONFIG = load_config(CONFIG_FILE) + + api = detect_api(CONFIG) + print("Detected API v{version}".format(version=api)) + check_config(CONFIG) - install(CONFIG) + install(CONFIG, SETTINGS_DIR) if api is Api.V3 and \ not check_if_running(api, CONFIG) and \ any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): From 19970bd7fa7ffa5863b03e396879162787b8d57f Mon Sep 17 00:00:00 2001 From: Andreas Grill Date: Sun, 17 May 2020 16:42:20 +0200 Subject: [PATCH 23/23] Fix installation of SelfControl - Check config file before trying to detect SelfControl version to warn if config file is invalid - Detect SelfControl 3.0 correctly - If a schedule is active during installation, correctly start the schedule - Fix logging in Catalina --- auto-selfcontrol.py | 64 +++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/auto-selfcontrol.py b/auto-selfcontrol.py index 0be3b66..9b360d4 100755 --- a/auto-selfcontrol.py +++ b/auto-selfcontrol.py @@ -6,7 +6,7 @@ import time from datetime import datetime import plistlib -import syslog +import logging.handlers import traceback import sys import re @@ -16,6 +16,14 @@ SETTINGS_DIR = '/usr/local/etc/auto-selfcontrol' +# Configure global logger +LOGGER = logging.getLogger("Auto-SelfControl") +LOGGER.setLevel(logging.INFO) +handler = logging.handlers.SysLogHandler('/var/run/syslog') +handler.setFormatter(logging.Formatter( + '%(name)s: [%(levelname)s] %(message)s')) +LOGGER.addHandler(handler) + class Api: V2 = 2 @@ -59,7 +67,7 @@ def detect_api(config): try: output = execSelfControl(config, ["--version"]) m = re.search( - get_selfcontrol_out_pattern(r'(\d+)\.\d+\.\d+'), output, re.MULTILINE) + get_selfcontrol_out_pattern(r'(\d+)\.\d+(\.\d+)*'), output, re.MULTILINE) if m is None: exit_with_error("Could not parse SelfControl version response!") if m and int(m.groups()[0]) >= Api.V3: @@ -77,12 +85,12 @@ def run(settings_dir): run_config = "{path}/run_config.json".format(path=settings_dir) if not os.path.exists(run_config): exit_with_error( - "Run config file could not be found in installation location, please make sure that you have Auto-SelfControl installed with '--install'") - - api = detect_api(CONFIG) - print("Detected API v{version}".format(version=api)) + "Run config file could not be found in installation location, please make sure that you have Auto-SelfControl activated/installed") config = load_config(run_config) + api = detect_api(config) + print("> Detected API v{version}".format(version=api)) + if api is Api.V2: run_api_v2(config) elif api is Api.V3: @@ -94,8 +102,8 @@ def run_api_v2(config): if check_if_running(Api.V2, config): print "SelfControl is already running, exit" - syslog.syslog( - syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") + LOGGER.error( + "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) try: @@ -103,8 +111,8 @@ def run_api_v2(config): s for s in config["block-schedules"] if is_schedule_active(s)) except StopIteration: print("No Schedule is active at the moment.") - syslog.syslog(syslog.LOG_ALERT, - "No schedule is active at the moment. Shutting down.") + LOGGER.warn( + "No schedule is active at the moment. Shutting down.") exit(0) duration = get_duration_minutes( @@ -130,8 +138,8 @@ def run_api_v2(config): # Start SelfControl execSelfControl(config, ["--install"]) - syslog.syslog(syslog.LOG_ALERT, - "SelfControl started for {min} minute(s).".format(min=duration)) + LOGGER.info( + "SelfControl started for {min} minute(s).".format(min=duration)) def run_api_v3(config, settings_dir): @@ -139,8 +147,8 @@ def run_api_v3(config, settings_dir): if check_if_running(Api.V3, config): print "SelfControl is already running, exit" - syslog.syslog( - syslog.LOG_ALERT, "SelfControl is already running, ignore current execution of Auto-SelfControl.") + LOGGER.error( + "SelfControl is already running, ignore current execution of Auto-SelfControl.") exit(2) try: @@ -148,8 +156,7 @@ def run_api_v3(config, settings_dir): s for s in config["block-schedules"] if is_schedule_active(s)) except StopIteration: print("No Schedule is active at the moment.") - syslog.syslog(syslog.LOG_ALERT, - "No schedule is active at the moment. Shutting down.") + LOGGER.warn("No schedule is active at the moment. Shutting down.") exit(0) block_end_date = get_end_date_of_schedule(schedule) @@ -160,8 +167,8 @@ def run_api_v3(config, settings_dir): # Start SelfControl execSelfControl(config, ["--install", blocklist_path, block_end_date]) - syslog.syslog(syslog.LOG_ALERT, - "SelfControl started until {end} minute(s).".format(end=block_end_date)) + LOGGER.info("SelfControl started until {end} minute(s).".format( + end=block_end_date)) def get_selfcontrol_out_pattern(content_pattern): @@ -373,7 +380,7 @@ def check_config(config): msg = "It is not recommended to directly use SelfControl's blacklist. Please use the 'host-blacklist' " \ "setting instead." print(msg) - syslog.syslog(syslog.LOG_WARNING, msg) + LOGGER.warn(msg) def update_blocklist(blocklist_path, config, schedule): @@ -395,12 +402,12 @@ def excepthook(excType, excValue, tb): """ This function is called whenever an exception is not caught. """ err = "Uncaught exception:\n{}\n{}\n{}".format(str(excType), excValue, "".join(traceback.format_exception(excType, excValue, tb))) - syslog.syslog(syslog.LOG_CRIT, err) + LOGGER.error(err) print(err) def exit_with_error(message): - syslog.syslog(syslog.LOG_CRIT, message) + LOGGER.error(message) print("ERROR:") print(message) exit(1) @@ -409,8 +416,6 @@ def exit_with_error(message): if __name__ == "__main__": sys.excepthook = excepthook - syslog.openlog("Auto-SelfControl") - if os.geteuid() != 0: exit_with_error("Please make sure to run the script with elevated \ rights, such as:\nsudo python {file} \ @@ -426,16 +431,17 @@ def exit_with_error(message): else: CONFIG_FILE = find_config() CONFIG = load_config(CONFIG_FILE) + check_config(CONFIG) api = detect_api(CONFIG) - print("Detected API v{version}".format(version=api)) + print("> Detected API v{version}".format(version=api)) - check_config(CONFIG) install(CONFIG, SETTINGS_DIR) - if api is Api.V3 and \ - not check_if_running(api, CONFIG) and \ - any(s for s in CONFIG["block-schedules"] if is_schedule_active(s)): + schedule_is_active = any( + s for s in CONFIG["block-schedules"] if is_schedule_active(s)) + + if schedule_is_active and not check_if_running(api, CONFIG): print("> Active schedule found for SelfControl!") print("> Start SelfControl (this could take a few minutes)\n") - run_api_v2(CONFIG) + run(SETTINGS_DIR) print("\n> SelfControl was started.\n")