From 678b0090c4edea0d0abf765c2f0ea736d8fcefce Mon Sep 17 00:00:00 2001 From: Nemo Date: Sun, 24 Dec 2017 16:43:07 +0530 Subject: [PATCH 01/10] Upgrade to Python 3 --- hackertray/__init__.py | 16 ++++++++-------- hackertray/analytics.py | 2 +- hackertray/chrome.py | 2 +- hackertray/firefox.py | 2 +- hackertray/version.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/hackertray/__init__.py b/hackertray/__init__.py index f0af07a..3df0522 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -16,20 +16,20 @@ if(os.environ.get('TRAVIS')!='true'): try: import appindicator except ImportError: - import appindicator_replacement as appindicator + from . import appindicator_replacement as appindicator - from appindicator_replacement import get_icon_filename + from .appindicator_replacement import get_icon_filename import json import argparse from os.path import expanduser import signal -from hackernews import HackerNews -from chrome import Chrome -from firefox import Firefox -from version import Version -from analytics import Analytics +from .hackernews import HackerNews +from .chrome import Chrome +from .firefox import Firefox +from .version import Version +from .analytics import Analytics class HackerNewsApp: HN_URL_PREFIX = "https://news.ycombinator.com/item?id=" @@ -211,7 +211,7 @@ class HackerNewsApp: self.addItem(item) # Catch network errors except requests.exceptions.RequestException as e: - print "[+] There was an error in fetching news items" + print("[+] There was an error in fetching news items") finally: # Call every 10 minutes if not no_timer: diff --git a/hackertray/analytics.py b/hackertray/analytics.py index f1437aa..ffa4352 100644 --- a/hackertray/analytics.py +++ b/hackertray/analytics.py @@ -8,7 +8,7 @@ class Analytics: self.dnt = dnt self.tracker = Mixpanel(token) if(self.dnt == True): - print "[+] Analytics disabled" + print("[+] Analytics disabled") # Track an event # event - string containing the event name # data - data related to the event, defaults to {} diff --git a/hackertray/chrome.py b/hackertray/chrome.py index e08d9a4..4d6d3c0 100644 --- a/hackertray/chrome.py +++ b/hackertray/chrome.py @@ -1,4 +1,4 @@ -from __future__ import print_function + import sqlite3 import shutil import os diff --git a/hackertray/firefox.py b/hackertray/firefox.py index 56a2041..7cb4a57 100644 --- a/hackertray/firefox.py +++ b/hackertray/firefox.py @@ -1,4 +1,4 @@ -from __future__ import print_function + import sqlite3 import shutil import os diff --git a/hackertray/version.py b/hackertray/version.py index e6414d2..d102da5 100644 --- a/hackertray/version.py +++ b/hackertray/version.py @@ -18,10 +18,10 @@ class Version: current = Version.current() try: if pkg_resources.parse_version(latest) > pkg_resources.parse_version(current): - print "[+] New version " + latest + " is available" + print("[+] New version " + latest + " is available") return True else: return False except requests.exceptions.RequestException as e: - print "[+] There was an error in trying to fetch updates" + print("[+] There was an error in trying to fetch updates") return False \ No newline at end of file From e34d767ef096962cffec937c4a816b1cfa2e8164 Mon Sep 17 00:00:00 2001 From: Nemo Date: Sun, 24 Dec 2017 16:44:26 +0530 Subject: [PATCH 02/10] [ci] Adds python3 in travis --- .travis.yml | 1 + CHANGELOG.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index c8cb0aa..7a17b2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - "2.7" + - "3.6" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install requests diff --git a/CHANGELOG.md b/CHANGELOG.md index bcabc28..e80f06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ This file will only list released and supported versions, usually skipping over very minor updates. +Unreleased +========== + +* Python 3 upgrade + 3.0.0 ===== From 3afba6f3ebac4186d34b38a6f8f8a3b5fee85ea2 Mon Sep 17 00:00:00 2001 From: Nemo Date: Sun, 24 Dec 2017 18:55:20 +0530 Subject: [PATCH 03/10] pep8 changes --- hackertray/__init__.py | 39 +++++++++++++------------- hackertray/analytics.py | 10 +++++-- hackertray/appindicator_replacement.py | 1 + hackertray/chrome.py | 5 +++- hackertray/firefox.py | 7 +++-- hackertray/hackernews.py | 3 +- hackertray/version.py | 6 ++-- 7 files changed, 43 insertions(+), 28 deletions(-) diff --git a/hackertray/__init__.py b/hackertray/__init__.py index 3df0522..e33c1bb 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -5,7 +5,7 @@ import requests import platform import subprocess -if(os.environ.get('TRAVIS')!='true'): +if(os.environ.get('TRAVIS') != 'true'): import pygtk pygtk.require('2.0') @@ -31,13 +31,15 @@ from .firefox import Firefox from .version import Version from .analytics import Analytics + class HackerNewsApp: HN_URL_PREFIX = "https://news.ycombinator.com/item?id=" UPDATE_URL = "https://github.com/captn3m0/hackertray#upgrade" ABOUT_URL = "https://github.com/captn3m0/hackertray" MIXPANEL_TOKEN = "51a04e37dad59393c7371407e84a8050" + def __init__(self, args): - #Load the database + # Load the database home = expanduser("~") with open(home + '/.hackertray.json', 'a+') as content_file: content_file.seek(0) @@ -58,7 +60,7 @@ class HackerNewsApp: # create a menu self.menu = gtk.Menu() - #The default state is false, and it toggles when you click on it + # The default state is false, and it toggles when you click on it self.commentState = args.comments # create items for the menu - refresh, quit and a separator @@ -79,14 +81,14 @@ class HackerNewsApp: btnRefresh = gtk.MenuItem("Refresh") btnRefresh.show() - #the last parameter is for not running the timer + # the last parameter is for not running the timer btnRefresh.connect("activate", self.refresh, True, args.chrome) self.menu.append(btnRefresh) if Version.new_available(): btnUpdate = gtk.MenuItem("New Update Available") btnUpdate.show() - btnUpdate.connect('activate',self.showUpdate) + btnUpdate.connect('activate', self.showUpdate) self.menu.append(btnUpdate) btnQuit = gtk.MenuItem("Quit") @@ -106,7 +108,7 @@ class HackerNewsApp: launch_data['version'] = Version.current() launch_data['platform'] = platform.linux_distribution() try: - launch_data['browser'] = subprocess.check_output(["xdg-settings","get","default-web-browser"]).strip() + launch_data['browser'] = subprocess.check_output(["xdg-settings", "get", "default-web-browser"]).strip() except subprocess.CalledProcessError as e: launch_data['browser'] = "unknown" self.tracker.track('launch', launch_data) @@ -115,26 +117,25 @@ class HackerNewsApp: """Whether comments page is opened or not""" self.commentState = not self.commentState - def showUpdate(self,widget): + def showUpdate(self, widget): """Handle the update button""" webbrowser.open(HackerNewsApp.UPDATE_URL) # Remove the update button once clicked self.menu.remove(widget) self.tracker.visit(HackerNewsApp.UPDATE_URL) - def showAbout(self, widget): """Handle the about btn""" webbrowser.open(HackerNewsApp.ABOUT_URL) self.tracker.visit(HackerNewsApp.ABOUT_URL) - #ToDo: Handle keyboard interrupt properly + # ToDo: Handle keyboard interrupt properly def quit(self, widget, data=None): """ Handler for the quit button""" l = list(self.db) home = expanduser("~") - #truncate the file + # truncate the file with open(home + '/.hackertray.json', 'w+') as file: file.write(json.dumps(l)) @@ -148,8 +149,8 @@ class HackerNewsApp: def open(self, widget, event=None, data=None): """Opens the link in the web browser""" - #We disconnect and reconnect the event in case we have - #to set it to active and we don't want the signal to be processed + # We disconnect and reconnect the event in case we have + # to set it to active and we don't want the signal to be processed if not widget.get_active(): widget.disconnect(widget.signal_id) widget.set_active(True) @@ -164,7 +165,7 @@ class HackerNewsApp: def addItem(self, item): """Adds an item to the menu""" - #This is in the case of YC Job Postings, which we skip + # This is in the case of YC Job Postings, which we skip if item['points'] == 0 or item['points'] is None: return @@ -184,7 +185,6 @@ class HackerNewsApp: i.show() def refresh(self, widget=None, no_timer=False, chrome_data_directory=None, firefox_data_directory=None): - """Refreshes the menu """ try: # Create an array of 20 false to denote matches in History @@ -197,12 +197,12 @@ class HackerNewsApp: if(firefox_data_directory): searchResults = self.mergeBoolArray(searchResults, Firefox.search(urls, firefox_data_directory)) - #Remove all the current stories + # Remove all the current stories for i in self.menu.get_children(): if hasattr(i, 'url'): self.menu.remove(i) - #Add back all the refreshed news + # Add back all the refreshed news for index, item in enumerate(data): item['history'] = searchResults[index] if item['url'].startswith('item?id='): @@ -223,13 +223,14 @@ class HackerNewsApp: original[index] = original[index] or patch[index] return original + def main(): parser = argparse.ArgumentParser(description='Hacker News in your System Tray') - parser.add_argument('-v','--version', action='version', version=Version.current()) - parser.add_argument('-c','--comments', dest='comments',action='store_true', help="Load the HN comments link for the article as well") + parser.add_argument('-v', '--version', action='version', version=Version.current()) + parser.add_argument('-c', '--comments', dest='comments', action='store_true', help="Load the HN comments link for the article as well") parser.add_argument('--chrome', dest='chrome', help="Specify a Google Chrome Profile directory to use for matching chrome history") parser.add_argument('--firefox', dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") - parser.add_argument('--dnt', dest='dnt',action='store_true', help="Disable all analytics (Do Not Track)") + parser.add_argument('--dnt', dest='dnt', action='store_true', help="Disable all analytics (Do Not Track)") parser.set_defaults(comments=False) parser.set_defaults(dnt=False) args = parser.parse_args() diff --git a/hackertray/analytics.py b/hackertray/analytics.py index ffa4352..8498b6c 100644 --- a/hackertray/analytics.py +++ b/hackertray/analytics.py @@ -1,9 +1,11 @@ from mixpanel import Mixpanel + class Analytics: # Setup analytics. # dnt - do not track. Disables tracking if True # token - The mixpanel token + def __init__(self, dnt, token): self.dnt = dnt self.tracker = Mixpanel(token) @@ -12,12 +14,14 @@ class Analytics: # Track an event # event - string containing the event name # data - data related to the event, defaults to {} - def track(self, event, data = {}): + + def track(self, event, data={}): if(self.dnt == False): # All events are tracked anonymously self.tracker.track("anonymous", event, data) # Track a visit to a URL - # The url maybe an HN submission or + # The url maybe an HN submission or # some meta-url pertaining to hackertray + def visit(self, url): - self.track('visit', {"link":url}) \ No newline at end of file + self.track('visit', {"link": url}) diff --git a/hackertray/appindicator_replacement.py b/hackertray/appindicator_replacement.py index 8ea0171..34b8722 100644 --- a/hackertray/appindicator_replacement.py +++ b/hackertray/appindicator_replacement.py @@ -35,6 +35,7 @@ def get_icon_filename(icon_name): # The main class class Indicator: # Constructor + def __init__(self, unknown, icon, category): # Store the settings self.inactive_icon = get_icon_filename(icon) diff --git a/hackertray/chrome.py b/hackertray/chrome.py index 4d6d3c0..c3f0008 100644 --- a/hackertray/chrome.py +++ b/hackertray/chrome.py @@ -4,8 +4,10 @@ import shutil import os import sys + class Chrome: HISTORY_TMP_LOCATION = '/tmp/hackertray.chrome' + @staticmethod def search(urls, config_folder_path): Chrome.setup(config_folder_path) @@ -13,13 +15,14 @@ class Chrome: db = conn.cursor() result = [] for url in urls: - db_result = db.execute('SELECT url from urls WHERE url=:url',{"url":url}) + db_result = db.execute('SELECT url from urls WHERE url=:url', {"url": url}) if(db.fetchone() == None): result.append(False) else: result.append(True) os.remove(Chrome.HISTORY_TMP_LOCATION) return result + @staticmethod def setup(config_folder_path): file_name = os.path.abspath(config_folder_path+'/History') diff --git a/hackertray/firefox.py b/hackertray/firefox.py index 7cb4a57..1f7b78c 100644 --- a/hackertray/firefox.py +++ b/hackertray/firefox.py @@ -4,9 +4,11 @@ import shutil import os import sys + class Firefox: HISTORY_TMP_LOCATION = '/tmp/hackertray.firefox' HISTORY_FILE_NAME = '/places.sqlite' + @staticmethod def search(urls, config_folder_path): Firefox.setup(config_folder_path) @@ -14,17 +16,18 @@ class Firefox: db = conn.cursor() result = [] for url in urls: - db_result = db.execute('SELECT url from moz_places WHERE url=:url',{"url":url}) + db_result = db.execute('SELECT url from moz_places WHERE url=:url', {"url": url}) if(db.fetchone() == None): result.append(False) else: result.append(True) os.remove(Firefox.HISTORY_TMP_LOCATION) return result + @staticmethod def setup(config_folder_path): file_name = os.path.abspath(config_folder_path+Firefox.HISTORY_FILE_NAME) if not os.path.isfile(file_name): print("ERROR: ", "Could not find Firefox history file", file=sys.stderr) sys.exit(1) - shutil.copyfile(file_name, Firefox.HISTORY_TMP_LOCATION) \ No newline at end of file + shutil.copyfile(file_name, Firefox.HISTORY_TMP_LOCATION) diff --git a/hackertray/hackernews.py b/hackertray/hackernews.py index 9857e45..d21ff37 100644 --- a/hackertray/hackernews.py +++ b/hackertray/hackernews.py @@ -7,6 +7,7 @@ urls = [ class HackerNews: + @staticmethod def getHomePage(): random.shuffle(urls) @@ -15,4 +16,4 @@ class HackerNews: try: return r.json() except ValueError: - continue \ No newline at end of file + continue diff --git a/hackertray/version.py b/hackertray/version.py index d102da5..82c8114 100644 --- a/hackertray/version.py +++ b/hackertray/version.py @@ -1,8 +1,10 @@ import requests import pkg_resources + class Version: PYPI_URL = "https://pypi.python.org/pypi/hackertray/json" + @staticmethod def latest(): res = requests.get(Version.PYPI_URL).json() @@ -14,7 +16,7 @@ class Version: @staticmethod def new_available(): - latest = Version.latest() + latest = Version.latest() current = Version.current() try: if pkg_resources.parse_version(latest) > pkg_resources.parse_version(current): @@ -24,4 +26,4 @@ class Version: return False except requests.exceptions.RequestException as e: print("[+] There was an error in trying to fetch updates") - return False \ No newline at end of file + return False From 6abaa46c8fe77c3dbe485e81c80a0c43db5dd1f0 Mon Sep 17 00:00:00 2001 From: Nemo Date: Sun, 14 Jun 2020 22:38:14 +0530 Subject: [PATCH 04/10] Removes tracking completely --- README.md | 5 ++--- hackertray/__init__.py | 12 +----------- hackertray/analytics.py | 27 --------------------------- setup.py | 6 +----- 4 files changed, 4 insertions(+), 46 deletions(-) delete mode 100644 hackertray/analytics.py diff --git a/README.md b/README.md index 0c55bee..cecf270 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ After that, you can run `hackertray` from anywhere and it will run. You can now add it to your OS dependent session autostart method. In Ubuntu, you can access it via: -1. System > Preferences > Sessions +1. System > Preferences > Sessions (OR) 2. System > Preferences > Startup Applications @@ -60,7 +60,6 @@ HackerTray accepts its various options via the command line. Run `hackertray -h` 1. `-c`: Enables comments support. Clicking on links will also open the comments page on HN. Can be switched off via the UI, but the setting is not remembered. 2. `--chrome PROFILE-PATH`: Specifying a profile path to a chrome directory will make HackerTray read the Chrome History file to mark links as read. Links are checked once every 5 minutes, which is when the History file is copied (to override the lock in case Chrome is open), searched using sqlite and deleted. This feature is still experimental. 3. `--firefox PROFILE-PATH`: Specify path to a firefox profile directory. HackerTray will read your firefox history from this profile, and use it to mark links as read. -4. `--dnt`: Disable analytics. Hackertray will no longer collect any sort of analytics. I'd prefer it if you left out this switch, as it helps me improve hackertray by understanding how its being used. Note that the `--chrome` and `--firefox` options are independent, and can be used together. However, they cannot be specified multiple times (so reading from 2 chrome profiles is not possible). @@ -103,7 +102,7 @@ To develop on hackertray, or to test out experimental versions, do the following ## Analytics -To help improve the project and learn how its being used, I've added Analytics in hackertray. The `--dnt` switch disables all analytics so that you can opt-out if desired. All data is collected anonymously, with no machine id or user-identifying information being sent back. To learn more, and see which events are being tracked, see the [Analytics](https://github.com/captn3m0/hackertray/wiki/Analytics) wiki page. +**No more tracking**. All data every collected for this project has been deleted. You can see [the wiki](https://github.com/captn3m0/hackertray/wiki/Analytics) for what all was collected earlier. ## Credits diff --git a/hackertray/__init__.py b/hackertray/__init__.py index e33c1bb..6cc0212 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -29,14 +29,12 @@ from .hackernews import HackerNews from .chrome import Chrome from .firefox import Firefox from .version import Version -from .analytics import Analytics class HackerNewsApp: HN_URL_PREFIX = "https://news.ycombinator.com/item?id=" UPDATE_URL = "https://github.com/captn3m0/hackertray#upgrade" ABOUT_URL = "https://github.com/captn3m0/hackertray" - MIXPANEL_TOKEN = "51a04e37dad59393c7371407e84a8050" def __init__(self, args): # Load the database @@ -49,9 +47,6 @@ class HackerNewsApp: except ValueError: self.db = set() - # Setup analytics - self.tracker = Analytics(args.dnt, HackerNewsApp.MIXPANEL_TOKEN) - # create an indicator applet self.ind = appindicator.Indicator("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS) self.ind.set_status(appindicator.STATUS_ACTIVE) @@ -111,7 +106,7 @@ class HackerNewsApp: launch_data['browser'] = subprocess.check_output(["xdg-settings", "get", "default-web-browser"]).strip() except subprocess.CalledProcessError as e: launch_data['browser'] = "unknown" - self.tracker.track('launch', launch_data) + def toggleComments(self, widget): """Whether comments page is opened or not""" @@ -122,12 +117,10 @@ class HackerNewsApp: webbrowser.open(HackerNewsApp.UPDATE_URL) # Remove the update button once clicked self.menu.remove(widget) - self.tracker.visit(HackerNewsApp.UPDATE_URL) def showAbout(self, widget): """Handle the about btn""" webbrowser.open(HackerNewsApp.ABOUT_URL) - self.tracker.visit(HackerNewsApp.ABOUT_URL) # ToDo: Handle keyboard interrupt properly def quit(self, widget, data=None): @@ -140,7 +133,6 @@ class HackerNewsApp: file.write(json.dumps(l)) gtk.main_quit() - self.tracker.track('quit') def run(self): signal.signal(signal.SIGINT, self.quit) @@ -161,7 +153,6 @@ class HackerNewsApp: if self.commentState: webbrowser.open(self.HN_URL_PREFIX + str(widget.hn_id)) - self.tracker.visit(widget.url) def addItem(self, item): """Adds an item to the menu""" @@ -230,7 +221,6 @@ def main(): parser.add_argument('-c', '--comments', dest='comments', action='store_true', help="Load the HN comments link for the article as well") parser.add_argument('--chrome', dest='chrome', help="Specify a Google Chrome Profile directory to use for matching chrome history") parser.add_argument('--firefox', dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") - parser.add_argument('--dnt', dest='dnt', action='store_true', help="Disable all analytics (Do Not Track)") parser.set_defaults(comments=False) parser.set_defaults(dnt=False) args = parser.parse_args() diff --git a/hackertray/analytics.py b/hackertray/analytics.py deleted file mode 100644 index 8498b6c..0000000 --- a/hackertray/analytics.py +++ /dev/null @@ -1,27 +0,0 @@ -from mixpanel import Mixpanel - - -class Analytics: - # Setup analytics. - # dnt - do not track. Disables tracking if True - # token - The mixpanel token - - def __init__(self, dnt, token): - self.dnt = dnt - self.tracker = Mixpanel(token) - if(self.dnt == True): - print("[+] Analytics disabled") - # Track an event - # event - string containing the event name - # data - data related to the event, defaults to {} - - def track(self, event, data={}): - if(self.dnt == False): - # All events are tracked anonymously - self.tracker.track("anonymous", event, data) - # Track a visit to a URL - # The url maybe an HN submission or - # some meta-url pertaining to hackertray - - def visit(self, url): - self.track('visit', {"link": url}) diff --git a/setup.py b/setup.py index 8c2ce08..c259950 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,7 @@ import sys from setuptools import setup from setuptools import find_packages - requirements = ['requests'] -if sys.version_info < (2, 7): - requirements.append('argparse') setup(name='hackertray', version='3.0.0', @@ -22,8 +19,7 @@ setup(name='hackertray', 'hackertray.data': ['hacker-tray.png'] }, install_requires=[ - 'requests>=2.2.1', - 'mixpanel-py>=3.0.0' + 'requests>=2.23.0' ], entry_points={ 'console_scripts': ['hackertray = hackertray:main'], From f68ea6e9c40a365f10260011c2624acf5fed3161 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:03:39 +0530 Subject: [PATCH 05/10] Adds support for default firefox profile --- hackertray/firefox.py | 21 ++++++++++++++++++++- test/firefox_test.py | 23 ++++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/hackertray/firefox.py b/hackertray/firefox.py index 1f7b78c..187a29e 100644 --- a/hackertray/firefox.py +++ b/hackertray/firefox.py @@ -3,12 +3,31 @@ import sqlite3 import shutil import os import sys - +from pathlib import Path +import configparser class Firefox: HISTORY_TMP_LOCATION = '/tmp/hackertray.firefox' HISTORY_FILE_NAME = '/places.sqlite' + @staticmethod + def default_firefox_profile_path(): + profile_file_path = Path.home().joinpath(".mozilla/firefox/profiles.ini") + profile_path = None + if (os.path.exists(profile_file_path)): + parser = configparser.ConfigParser() + parser.read(profile_file_path) + for section in parser.sections(): + if parser[section]["Default"] == "1": + if parser[section]["IsRelative"] == "1": + profile_path = str(Path.home().joinpath(".mozilla/firefox/").joinpath(parser[section]["Path"])) + else: + profile_path = parser[section]["Path"] + if profile_path and Path.is_dir(Path(profile_path)): + return profile_path + else: + raise RuntimeError("Couldn't find default Firefox profile") + @staticmethod def search(urls, config_folder_path): Firefox.setup(config_folder_path) diff --git a/test/firefox_test.py b/test/firefox_test.py index 5c5d28a..faf504f 100644 --- a/test/firefox_test.py +++ b/test/firefox_test.py @@ -1,10 +1,12 @@ import unittest import os +import pathlib +from pathlib import Path from hackertray import Firefox -class ChromeTest(unittest.TestCase): - def runTest(self): +class FirefoxTest(unittest.TestCase): + def test_history(self): config_folder_path = os.getcwd()+'/test/' data = Firefox.search([ "http://www.hckrnews.com/", @@ -12,4 +14,19 @@ class ChromeTest(unittest.TestCase): "http://wiki.ubuntu.com/", "http://invalid_url/"], config_folder_path) - self.assertTrue(data == [True,True,True,False]) \ No newline at end of file + self.assertTrue(data == [True,True,True,False]) + + def test_default(self): + test_default_path = Path.home().joinpath(".mozilla/firefox/x0ran0o9.default") + if(os.environ.get('TRAVIS') == 'true'): + if not os.path.exists(test_default_path): + os.makedirs(test_default_path) + with open(str(Path.home().joinpath('.mozilla/firefox/profiles.ini')), 'w') as f: + f.write(""" +[Profile1] +Name=default +IsRelative=1 +Path=x0ran0o9.default +Default=1 + """) + self.assertTrue(Firefox.default_firefox_profile_path()==Path.home().joinpath(".mozilla/firefox/x0ran0o9.default")) From 01ad187150cdc6801acc758a72859f1c0f401379 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:03:57 +0530 Subject: [PATCH 06/10] Finish PyGtk upgrade (7 years in the making) --- hackertray/__init__.py | 45 +++++++++----------------- hackertray/appindicator_replacement.py | 13 ++++---- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/hackertray/__init__.py b/hackertray/__init__.py index 6cc0212..0bf4844 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -2,15 +2,12 @@ import os import requests -import platform import subprocess if(os.environ.get('TRAVIS') != 'true'): - import pygtk - - pygtk.require('2.0') - import gtk - + import gi + gi.require_version('Gtk', '3.0') + from gi.repository import Gtk,GLib import webbrowser try: @@ -53,40 +50,40 @@ class HackerNewsApp: self.ind.set_icon(get_icon_filename("hacker-tray.png")) # create a menu - self.menu = gtk.Menu() + self.menu = Gtk.Menu() # The default state is false, and it toggles when you click on it self.commentState = args.comments # create items for the menu - refresh, quit and a separator - menuSeparator = gtk.SeparatorMenuItem() + menuSeparator = Gtk.SeparatorMenuItem() menuSeparator.show() self.menu.append(menuSeparator) - btnComments = gtk.CheckMenuItem("Show Comments") + btnComments = Gtk.CheckMenuItem("Show Comments") btnComments.show() btnComments.set_active(args.comments) btnComments.connect("activate", self.toggleComments) self.menu.append(btnComments) - btnAbout = gtk.MenuItem("About") + btnAbout = Gtk.MenuItem("About") btnAbout.show() btnAbout.connect("activate", self.showAbout) self.menu.append(btnAbout) - btnRefresh = gtk.MenuItem("Refresh") + btnRefresh = Gtk.MenuItem("Refresh") btnRefresh.show() # the last parameter is for not running the timer btnRefresh.connect("activate", self.refresh, True, args.chrome) self.menu.append(btnRefresh) if Version.new_available(): - btnUpdate = gtk.MenuItem("New Update Available") + btnUpdate = Gtk.MenuItem("New Update Available") btnUpdate.show() btnUpdate.connect('activate', self.showUpdate) self.menu.append(btnUpdate) - btnQuit = gtk.MenuItem("Quit") + btnQuit = Gtk.MenuItem("Quit") btnQuit.show() btnQuit.connect("activate", self.quit) self.menu.append(btnQuit) @@ -95,18 +92,6 @@ class HackerNewsApp: self.ind.set_menu(self.menu) self.refresh(chrome_data_directory=args.chrome, firefox_data_directory=args.firefox) - self.launch_analytics(args) - - def launch_analytics(self, args): - # Now that we're all done with the boot, send a beacone home - launch_data = vars(args) - launch_data['version'] = Version.current() - launch_data['platform'] = platform.linux_distribution() - try: - launch_data['browser'] = subprocess.check_output(["xdg-settings", "get", "default-web-browser"]).strip() - except subprocess.CalledProcessError as e: - launch_data['browser'] = "unknown" - def toggleComments(self, widget): """Whether comments page is opened or not""" @@ -132,11 +117,11 @@ class HackerNewsApp: with open(home + '/.hackertray.json', 'w+') as file: file.write(json.dumps(l)) - gtk.main_quit() + Gtk.main_quit() def run(self): signal.signal(signal.SIGINT, self.quit) - gtk.main() + Gtk.main() return 0 def open(self, widget, event=None, data=None): @@ -160,7 +145,7 @@ class HackerNewsApp: if item['points'] == 0 or item['points'] is None: return - i = gtk.CheckMenuItem( + i = Gtk.CheckMenuItem( "(" + str(item['points']).zfill(3) + "/" + str(item['comments_count']).zfill(3) + ") " + item['title']) visited = item['history'] or item['id'] in self.db @@ -206,7 +191,7 @@ class HackerNewsApp: finally: # Call every 10 minutes if not no_timer: - gtk.timeout_add(10 * 30 * 1000, self.refresh, widget, no_timer, chrome_data_directory) + GLib.timeout_add(10 * 30 * 1000, self.refresh, widget, no_timer, chrome_data_directory) # Merges two boolean arrays, using OR operation against each pair def mergeBoolArray(self, original, patch): @@ -220,7 +205,7 @@ def main(): parser.add_argument('-v', '--version', action='version', version=Version.current()) parser.add_argument('-c', '--comments', dest='comments', action='store_true', help="Load the HN comments link for the article as well") parser.add_argument('--chrome', dest='chrome', help="Specify a Google Chrome Profile directory to use for matching chrome history") - parser.add_argument('--firefox', dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") + parser.add_argument('--firefox', default=self.default_firefox_profile(), dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") parser.set_defaults(comments=False) parser.set_defaults(dnt=False) args = parser.parse_args() diff --git a/hackertray/appindicator_replacement.py b/hackertray/appindicator_replacement.py index 34b8722..c9b5cd7 100644 --- a/hackertray/appindicator_replacement.py +++ b/hackertray/appindicator_replacement.py @@ -9,8 +9,7 @@ #========================= # We require PyGTK -import gtk -import gobject +from gi.repository import Gtk,GLib # We also need os and sys import os @@ -42,7 +41,7 @@ class Indicator: self.active_icon = "" # Blank until the user calls set_attention_icon # Create the status icon - self.icon = gtk.StatusIcon() + self.icon = Gtk.StatusIcon() # Initialize to the default icon self.icon.set_from_file(self.inactive_icon) @@ -81,7 +80,7 @@ class Indicator: def show_menu(self, widget): # Show the menu - self.menu.popup(None, None, None, 0, 0) + self.menu.popup(None, None, None, 0, 0, Gtk.get_current_event_time()) # Get the location and size of the window mouse_rect = self.menu.get_window().get_frame_extents() @@ -92,7 +91,7 @@ class Indicator: self.bottom = self.y + mouse_rect.height # Set a timer to poll the menu - self.timer = gobject.timeout_add(100, self.check_mouse) + self.timer = GLib.timeout_add(100, self.check_mouse) def check_mouse(self): if not self.menu.get_window().is_visible(): @@ -101,9 +100,9 @@ class Indicator: # Now check the global mouse coords root = self.menu.get_screen().get_root_window() - x, y, z = root.get_pointer() + _,x,y,_ = root.get_pointer() - if x < self.x or x > self.right or y < self.y or y > self.bottom: + if (x < (self.x-10)) or (x > self.right) or (y < (self.y+10)) or (y > self.bottom): self.hide_menu() else: return True From ff57182c3c4e7b84606ac6fc52d1e73e1d53ccd0 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:16:34 +0530 Subject: [PATCH 07/10] Support default firefox profile --- .gitignore | 5 ++++- .travis.yml | 14 ++++++++++---- CHANGELOG.md | 7 ++++++- hackertray/__init__.py | 7 ++++--- hackertray/firefox.py | 8 ++++---- setup.py | 4 ++-- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 966f7c7..d3979dc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ var/ # Installer logs pip-log.txt -pip-delete-this-directory.txt \ No newline at end of file +pip-delete-this-directory.txt + +pyvenv.cfg +bin/ diff --git a/.travis.yml b/.travis.yml index 7a17b2b..cb920e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,20 @@ language: python python: - - "2.7" + # https://endoflife.date/python + # goes away in sep 2020 + - "3.5" + # goes away in dec 2021 - "3.6" + # goes away in jun 2023 + - "3.7" + # goes away in oct 2024 + - "3.8" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: +install: - pip install requests - pip install nose - - pip install mixpanel-py # command to run tests, e.g. python setup.py test -script: nosetests --nocapture +script: nosetests --nocapture notifications: email: on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md index e80f06c..a1b9e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,12 @@ This file will only list released and supported versions, usually skipping over Unreleased ========== -* Python 3 upgrade +4.0.0 +===== + +* Upgrades to Python 3.0. Python 2 is no longer supported +* Switches from PyGtk to PyGObject. +* AppIndicator is no longer supported, because it is Python 2 only 3.0.0 ===== diff --git a/hackertray/__init__.py b/hackertray/__init__.py index 0bf4844..2b70fc9 100644 --- a/hackertray/__init__.py +++ b/hackertray/__init__.py @@ -87,10 +87,11 @@ class HackerNewsApp: btnQuit.show() btnQuit.connect("activate", self.quit) self.menu.append(btnQuit) - self.menu.show() - self.ind.set_menu(self.menu) + + if args.firefox == "auto": + args.firefox = Firefox.default_firefox_profile_path() self.refresh(chrome_data_directory=args.chrome, firefox_data_directory=args.firefox) def toggleComments(self, widget): @@ -205,7 +206,7 @@ def main(): parser.add_argument('-v', '--version', action='version', version=Version.current()) parser.add_argument('-c', '--comments', dest='comments', action='store_true', help="Load the HN comments link for the article as well") parser.add_argument('--chrome', dest='chrome', help="Specify a Google Chrome Profile directory to use for matching chrome history") - parser.add_argument('--firefox', default=self.default_firefox_profile(), dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history") + parser.add_argument('--firefox', dest='firefox', help="Specify a Firefox Profile directory to use for matching firefox history. Pass auto to automatically pick the default profile") parser.set_defaults(comments=False) parser.set_defaults(dnt=False) args = parser.parse_args() diff --git a/hackertray/firefox.py b/hackertray/firefox.py index 187a29e..ae4bd66 100644 --- a/hackertray/firefox.py +++ b/hackertray/firefox.py @@ -18,8 +18,8 @@ class Firefox: parser = configparser.ConfigParser() parser.read(profile_file_path) for section in parser.sections(): - if parser[section]["Default"] == "1": - if parser[section]["IsRelative"] == "1": + if parser.has_option(section,"Default") and parser[section]["Default"] == "1": + if parser.has_option(section,"IsRelative") and parser[section]["IsRelative"] == "1": profile_path = str(Path.home().joinpath(".mozilla/firefox/").joinpath(parser[section]["Path"])) else: profile_path = parser[section]["Path"] @@ -45,8 +45,8 @@ class Firefox: @staticmethod def setup(config_folder_path): - file_name = os.path.abspath(config_folder_path+Firefox.HISTORY_FILE_NAME) + file_name = os.path.abspath(config_folder_path + Firefox.HISTORY_FILE_NAME) if not os.path.isfile(file_name): - print("ERROR: ", "Could not find Firefox history file", file=sys.stderr) + print("ERROR: Could not find Firefox history file, using %s" % file_name) sys.exit(1) shutil.copyfile(file_name, Firefox.HISTORY_TMP_LOCATION) diff --git a/setup.py b/setup.py index c259950..f158d92 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ setup(name='hackertray', description='Hacker News app that sits in your System Tray', long_description='HackerTray is a simple Hacker News Linux application that lets you view top HN stories in your System Tray. It relies on appindicator, so it is not guaranteed to work on all systems. It also provides a Gtk StatusIcon fallback in case AppIndicator is not available.', keywords='hacker news hn tray system tray icon hackertray', - url='http://captnemo.in/hackertray', - author='Abhay Rana', + url='https://captnemo.in/hackertray', + author='Abhay Rana (Nemo)', author_email='me@captnemo.in', license='MIT', packages=find_packages(), From f1141b233588cafc058ac72484199e3c71a0fc64 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:20:14 +0530 Subject: [PATCH 08/10] [docs] Update docs for new release --- CHANGELOG.md | 1 + README.md | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1b9e8b..f37501a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Unreleased 4.0.0 ===== +* Adds support for --firefox auto, picks the default firefox profile automatically * Upgrades to Python 3.0. Python 2 is no longer supported * Switches from PyGtk to PyGObject. * AppIndicator is no longer supported, because it is Python 2 only diff --git a/README.md b/README.md index cecf270..0a6ff4c 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ HackerTray accepts its various options via the command line. Run `hackertray -h` 1. `-c`: Enables comments support. Clicking on links will also open the comments page on HN. Can be switched off via the UI, but the setting is not remembered. 2. `--chrome PROFILE-PATH`: Specifying a profile path to a chrome directory will make HackerTray read the Chrome History file to mark links as read. Links are checked once every 5 minutes, which is when the History file is copied (to override the lock in case Chrome is open), searched using sqlite and deleted. This feature is still experimental. -3. `--firefox PROFILE-PATH`: Specify path to a firefox profile directory. HackerTray will read your firefox history from this profile, and use it to mark links as read. +3. `--firefox PROFILE-PATH`: Specify path to a firefox profile directory. HackerTray will read your firefox history from this profile, and use it to mark links as read. Pass `auto` as PROFILE-PATH to automatically read the default profile and use that. Note that the `--chrome` and `--firefox` options are independent, and can be used together. However, they cannot be specified multiple times (so reading from 2 chrome profiles is not possible). @@ -75,15 +75,15 @@ Replace `Default` with `Profile 1`, `Profile 2` or so on if you use multiple pro ### Firefox Profile Path -The default firefox profile path is `~/.mozilla/firefox/*.default`, where `*` denotes a random 8 digit string. You can also read `~/.mozilla/firefox/profiles.ini` to get a list of profiles. +The default firefox profile path is `~/.mozilla/firefox/*.default`, where `*` denotes a random 8 digit string. You can also read `~/.mozilla/firefox/profiles.ini` to get a list of profiles. Alternatively, just pass `auto` and HackerTray will pick the default profile automatically. ## Features 1. Minimalist Approach to HN 2. Opens links in your default browser -3. Remembers which links you opened +3. Remembers which links you opened, even if you opened them outside of HackerTray 4. Shows Points/Comment count in a simple format -5. Reads your Google Chrome History file to determine which links you've already read (even if you may not have opened them via HackerTray) +5. Reads your Google Chrome/Firefox History file to determine which links you've already read (even if you may not have opened them via HackerTray) ### Troubleshooting @@ -92,6 +92,8 @@ python-appindicator with `sudo apt-get install python-appindicator` +Note that appindicator is no longer supported in non-Ubuntu distros, because it only works on Python2. + ### Development To develop on hackertray, or to test out experimental versions, do the following: @@ -106,9 +108,9 @@ To develop on hackertray, or to test out experimental versions, do the following ## Credits -- Mark Rickert for [Hacker Bar](http://hackerbarapp.com/). +- Mark Rickert for [Hacker Bar](http://hackerbarapp.com/) (No longer active) - [Giridaran Manivannan](https://github.com/ace03uec) for troubleshooting instructions. ## Licence -Licenced under the [MIT Licence](http://nemo.mit-license.org/). +Licenced under the [MIT Licence](https://nemo.mit-license.org/). From 1a29800787b6204fe0808b2d98e5f390ed89e8b7 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:27:10 +0530 Subject: [PATCH 09/10] Version Bump --- README.md | 2 ++ setup.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a6ff4c..71d886d 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,8 @@ To develop on hackertray, or to test out experimental versions, do the following ## Analytics +On every launch, a request is made to `https://pypi.python.org/pypi/hackertray/json` to check the latest version. + **No more tracking**. All data every collected for this project has been deleted. You can see [the wiki](https://github.com/captn3m0/hackertray/wiki/Analytics) for what all was collected earlier. ## Credits diff --git a/setup.py b/setup.py index f158d92..ed461ff 100644 --- a/setup.py +++ b/setup.py @@ -6,9 +6,9 @@ from setuptools import find_packages requirements = ['requests'] setup(name='hackertray', - version='3.0.0', + version='4.0.0', description='Hacker News app that sits in your System Tray', - long_description='HackerTray is a simple Hacker News Linux application that lets you view top HN stories in your System Tray. It relies on appindicator, so it is not guaranteed to work on all systems. It also provides a Gtk StatusIcon fallback in case AppIndicator is not available.', + long_description='HackerTray is a simple Hacker News Linux application that lets you view top HN stories in your System Tray. It supports appindicator and falls back to Gtk StatusIcon otherwise.', keywords='hacker news hn tray system tray icon hackertray', url='https://captnemo.in/hackertray', author='Abhay Rana (Nemo)', From a37ef7236bb2854773e663db463ffcf92074e3ec Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 15 Jun 2020 00:30:05 +0530 Subject: [PATCH 10/10] [tests] Fix tests on Travis --- README.md | 10 ++++++---- hackertray/firefox.py | 2 +- test/firefox_test.py | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 71d886d..d6ad561 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,12 @@ Note that the `--chrome` and `--firefox` options are independent, and can be use ### Google Chrome Profile Path -Where your Profile is stored depends on which version of chrome you are using: +Where your Profile is stored depends on [which version of chrome you are using](https://chromium.googlesource.com/chromium/src.git/+/62.0.3202.58/docs/user_data_dir.md#linux): -- `google-chrome-stable`: `~/.config/google-chrome/Default/` -- `google-chrome-unstable`: `~/.config/google-chrome-unstable/Default/` -- `chromium`: `~/.config/chromium/Default/` +- [Chrome Stable] `~/.config/google-chrome/Default` +- [Chrome Beta] `~/.config/google-chrome-beta/Default` +- [Chrome Dev] `~/.config/google-chrome-unstable/Default` +- [Chromium] `~/.config/chromium/Default` Replace `Default` with `Profile 1`, `Profile 2` or so on if you use multiple profiles on Chrome. Note that the `--chrome` option accepts a `PROFILE-PATH`, not the History file itself. Also note that sometimes `~` might not be set, so you might need to use the complete path (such as `/home/nemo/.config/google-chrome/Default/`). @@ -112,6 +113,7 @@ On every launch, a request is made to `https://pypi.python.org/pypi/hackertray/j - Mark Rickert for [Hacker Bar](http://hackerbarapp.com/) (No longer active) - [Giridaran Manivannan](https://github.com/ace03uec) for troubleshooting instructions. +- [@cheeaun](https://github.com/cheeaun) for the [Unofficial Hacker News API](https://github.com/cheeaun/node-hnapi/) ## Licence diff --git a/hackertray/firefox.py b/hackertray/firefox.py index ae4bd66..08ea44d 100644 --- a/hackertray/firefox.py +++ b/hackertray/firefox.py @@ -12,7 +12,7 @@ class Firefox: @staticmethod def default_firefox_profile_path(): - profile_file_path = Path.home().joinpath(".mozilla/firefox/profiles.ini") + profile_file_path = str(Path.home().joinpath(".mozilla/firefox/profiles.ini")) profile_path = None if (os.path.exists(profile_file_path)): parser = configparser.ConfigParser() diff --git a/test/firefox_test.py b/test/firefox_test.py index faf504f..c95e7f8 100644 --- a/test/firefox_test.py +++ b/test/firefox_test.py @@ -19,8 +19,8 @@ class FirefoxTest(unittest.TestCase): def test_default(self): test_default_path = Path.home().joinpath(".mozilla/firefox/x0ran0o9.default") if(os.environ.get('TRAVIS') == 'true'): - if not os.path.exists(test_default_path): - os.makedirs(test_default_path) + if not os.path.exists(str(test_default_path)): + os.makedirs(str(test_default_path)) with open(str(Path.home().joinpath('.mozilla/firefox/profiles.ini')), 'w') as f: f.write(""" [Profile1] @@ -29,4 +29,4 @@ IsRelative=1 Path=x0ran0o9.default Default=1 """) - self.assertTrue(Firefox.default_firefox_profile_path()==Path.home().joinpath(".mozilla/firefox/x0ran0o9.default")) + self.assertTrue(Firefox.default_firefox_profile_path()==str(Path.home().joinpath(".mozilla/firefox/x0ran0o9.default")))