{"id":1719,"date":"2015-12-08T05:00:26","date_gmt":"2015-12-08T13:00:26","guid":{"rendered":"http:\/\/zackmdavis.net\/blog\/?p=1719"},"modified":"2015-12-29T14:46:17","modified_gmt":"2015-12-29T22:46:17","slug":"attentional-shunt","status":"publish","type":"post","link":"http:\/\/zackmdavis.net\/blog\/2015\/12\/attentional-shunt\/","title":{"rendered":"Attentional Shunt"},"content":{"rendered":"<pre><code>#!\/usr\/bin\/env python3\r\n\r\n# Copyright \u00a9 2015 Zack M. Davis\r\n\r\n# Permission is hereby granted, free of charge, to any person obtaining a copy\r\n# of this software and associated documentation files (the &quot;Software&quot;), to deal\r\n# in the Software without restriction, including without limitation the rights\r\n# to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\r\n# copies of the Software, and to permit persons to whom the Software is\r\n# furnished to do so, subject to the following conditions:\r\n\r\n# The above copyright notice and this permission notice shall be included in\r\n# all copies or substantial portions of the Software.\r\n\r\n# THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n# THE SOFTWARE.\r\n\r\n&quot;&quot;&quot;\r\nConfigure the machine to shunt traffic to distracting sites to localhost,\r\npreserving attention.\r\n&quot;&quot;&quot;\r\n\r\nimport os\r\nimport argparse\r\nimport subprocess\r\nimport sys\r\nfrom datetime import datetime, timedelta\r\n\r\nETC_HOSTS = os.path.join(os.sep, &#39;etc&#39;, &#39;hosts&#39;)\r\nHEADER = &quot;# below managed by attentional shunt&quot;\r\nINVERSE_COMMANDS = {&#39;enable&#39;: &quot;disable&quot;, &#39;disable&#39;: &quot;enable&quot;}\r\n\r\nDISTRACTING_HOSTS = (  # modify as needed\r\n    &#39;news.ycombinator.com&#39;,\r\n    &#39;math.stackexchange.com&#39;,\r\n    &#39;scifi.stackexchange.com&#39;,\r\n    &#39;worldbuilding.stackexchange.com&#39;,\r\n    &#39;workplace.stackexchange.com&#39;,\r\n    &#39;academia.stackexchange.com&#39;,\r\n    &#39;codereview.stackexchange.com&#39;,\r\n    &#39;puzzling.stackexchange.com&#39;,\r\n    &#39;slatestarcodex.com&#39;,\r\n    &#39;twitter.com&#39;,\r\n    &#39;www.facebook.com&#39;,\r\n    &#39;slatestarscratchpad.tumblr.com&#39;,\r\n)\r\nSHUNTING_LINES = &quot;\\n{}\\n{}\\n&quot;.format(\r\n    HEADER,\r\n    &#39;\\n&#39;.join(&quot;127.0.0.1 {}&quot;.format(domain)\r\n              for domain in DISTRACTING_HOSTS)\r\n)\r\n\r\n\r\ndef conditionally_reexec_with_sudo():\r\n    if os.geteuid() != 0:\r\n        os.execvp(&quot;sudo&quot;, [&quot;sudo&quot;] + sys.argv)\r\n\r\n\r\ndef enable_shunt():\r\n    if is_enabled():\r\n        return  # nothing to do\r\n    with open(ETC_HOSTS, &#39;a&#39;) as etc_hosts:\r\n        etc_hosts.write(SHUNTING_LINES)\r\n\r\n\r\ndef disable_shunt():\r\n    with open(ETC_HOSTS) as etc_hosts:\r\n        content = etc_hosts.read()\r\n    if SHUNTING_LINES not in content:\r\n        return  # nothing to do\r\n    with open(ETC_HOSTS, &#39;w&#39;) as etc_hosts:\r\n        etc_hosts.write(content.replace(SHUNTING_LINES, &#39;&#39;))\r\n\r\n\r\ndef is_enabled():\r\n    with open(ETC_HOSTS) as etc_hosts:\r\n        content = etc_hosts.read()\r\n    return HEADER in content\r\n\r\n\r\ndef status():\r\n    state = &quot;enabled&quot; if is_enabled() else &quot;disabled&quot;\r\n    print(&quot;attentional shunt is {}&quot;.format(state))\r\n\r\n\r\ndef schedule(command, when):  # requires `at` job-scheduling utility\r\n    timestamp = when.strftime(&quot;%H:%M %Y-%m-%d&quot;)\r\n    at_command = [&#39;at&#39;, timestamp]\r\n    at = subprocess.Popen(\r\n        at_command,\r\n        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE\r\n    )\r\n    at.communicate(command.encode())\r\n\r\n\r\nif __name__ == &quot;__main__&quot;:\r\n    arg_parser = argparse.ArgumentParser(description=__doc__)\r\n    arg_parser.add_argument(&#39;command&#39;,\r\n                            choices=(&quot;enable&quot;, &quot;disable&quot;, &quot;status&quot;))\r\n    arg_parser.add_argument(&#39;duration&#39;, nargs=&#39;?&#39;, type=int,\r\n                            help=(&quot;revert state change after this many &quot;\r\n                                  &quot;minutes&quot;))\r\n    args = arg_parser.parse_args()\r\n    if args.command == &quot;status&quot;:\r\n        status()\r\n    else:\r\n        conditionally_reexec_with_sudo()\r\n        if args.command == &quot;enable&quot;:\r\n            enable_shunt()\r\n        elif args.command == &quot;disable&quot;:\r\n            disable_shunt()\r\n\r\n        if args.duration:\r\n            now = datetime.now()\r\n            inverse_command = INVERSE_COMMANDS[args.command]\r\n            schedule(\r\n                &quot;{} {}&quot;.format(os.path.realpath(__file__), inverse_command),\r\n                now + timedelta(minutes=args.duration)\r\n            )<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>#!\/usr\/bin\/env python3 # Copyright \u00a9 2015 Zack M. Davis # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the &quot;Software&quot;), to deal # in the Software without &hellip; <a href=\"http:\/\/zackmdavis.net\/blog\/2015\/12\/attentional-shunt\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[20],"tags":[24,21],"_links":{"self":[{"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/posts\/1719"}],"collection":[{"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/comments?post=1719"}],"version-history":[{"count":2,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/posts\/1719\/revisions"}],"predecessor-version":[{"id":1755,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/posts\/1719\/revisions\/1755"}],"wp:attachment":[{"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/media?parent=1719"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/categories?post=1719"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/zackmdavis.net\/blog\/wp-json\/wp\/v2\/tags?post=1719"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}