mirror of
https://github.com/captn3m0/hackertray.git
synced 2024-09-28 22:23:02 +00:00
Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
ef43ce5942 | |||
52a2dbc94b | |||
be8b61530f | |||
249f3f1b84 | |||
b751bcd74c | |||
30a38bd769 | |||
a78796e029 | |||
c10614c2bd | |||
5736b65bb4 | |||
3117a7948e | |||
0a48248739 | |||
74d26ccffa | |||
64cc856ed1 | |||
a37ef7236b | |||
1a29800787 | |||
f1141b2335 | |||
ff57182c3c | |||
01ad187150 | |||
f68ea6e9c4 | |||
6abaa46c8f | |||
3afba6f3eb | |||
e34d767ef0 | |||
678b0090c4 | |||
297ef91497 | |||
fbd2227a3c | |||
|
e81b98929b | ||
|
97c3414cb0 | ||
|
aef61a556c | ||
|
8f17672014 | ||
|
a1b500e58d | ||
|
591f6454f2 | ||
|
4224c9c198 | ||
|
c7a1c653d9 | ||
|
a5a4b54d97 | ||
|
6255917af4 | ||
|
bd322e5191 | ||
|
0b0b5c92b8 | ||
|
c7cea3d278 | ||
|
0c314e690b | ||
|
4edb2a2053 | ||
|
b9ebeaebce | ||
|
96a2364c04 | ||
|
5a73dd9d93 | ||
|
11c02ba898 | ||
|
6c93751d73 | ||
|
93c9647b3b | ||
|
d9e6e51722 | ||
|
c4b195af3d | ||
|
ecd2011875 | ||
|
607a9804f3 | ||
|
279034854c | ||
|
59def73822 | ||
|
d2246edd4f | ||
|
76b64e7fb6 | ||
|
912aa745d8 | ||
|
7db719c21e | ||
|
f297ea4019 | ||
|
b2dda62fe0 | ||
|
9a6d60be90 | ||
|
0b1c1bf2ad | ||
|
6298da5f5b | ||
|
6b52720efd | ||
|
83b5c48bd2 | ||
|
ae7fae9121 | ||
|
a0ebb6baf8 | ||
|
ce4ceec31f | ||
|
0b4d6b4424 | ||
|
ecc42c67ec | ||
|
da7998462f | ||
|
b1bcdf1024 | ||
|
578830a2e6 | ||
|
6b57097334 | ||
|
9dbc5d5ef8 | ||
|
0586db9d72 | ||
|
992961071c | ||
|
51a6be1a1d | ||
|
64d500c328 | ||
|
b20fa07d95 | ||
|
b2e3b2ca43 | ||
|
0b1353131d | ||
e48521b1ed | |||
|
1e83a78129 | ||
|
2b4dd31a65 | ||
|
2b45eacfd8 | ||
5a9b4e6b1d | |||
|
50b1e7748a | ||
|
27fb60cc26 | ||
|
ea63201f14 | ||
|
c5bdb2a362 | ||
|
3536201784 |
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ko_fi: captn3m0
|
||||||
|
liberapay: captn3m0
|
||||||
|
github: captn3m0
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -18,3 +18,7 @@ var/
|
|||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
pyvenv.cfg
|
||||||
|
bin/
|
||||||
|
.coverage
|
||||||
|
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
# 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:
|
||||||
|
- pip install requests
|
||||||
|
- pip install nose
|
||||||
|
- pip install coverage
|
||||||
|
- pip install coveralls
|
||||||
|
# command to run tests, e.g. python setup.py test
|
||||||
|
script: coverage run --source=hackertray $(which nosetests)
|
||||||
|
after_success: coveralls
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: never
|
||||||
|
on_failure: always
|
46
CHANGELOG.md
Normal file
46
CHANGELOG.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
This file will only list released and supported versions, usually skipping over very minor updates.
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
==========
|
||||||
|
|
||||||
|
4.0.2
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Adds a --reverse flag for users with bar at the bottom of their screen
|
||||||
|
* Uses markup to keep points in fixed-width, so titles are more readable
|
||||||
|
* Removes the buggy hover-out behaviour (non-appindicator). You now need to click elsewhere to close the menu
|
||||||
|
|
||||||
|
4.0.1
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Changes "Show Comments" entry to a radio menu item
|
||||||
|
|
||||||
|
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
|
||||||
|
* Removed all MixPanel tracking.
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Oct 3, 2014
|
||||||
|
* Major release.
|
||||||
|
* Firefox support behind `--firefox` flag
|
||||||
|
* Analytics support. Can be disabled using `--dnt` flag
|
||||||
|
* Hovering now shows url, timestamp, and uploader nick
|
||||||
|
|
||||||
|
2.3.2
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Sep 27, 2014
|
||||||
|
* Adds proxy support
|
||||||
|
|
||||||
|
|
||||||
|
2.2.0
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Adds support for using chrome history behind the `--chrome` flag.
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2021 Abhay Rana
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -1 +1,2 @@
|
|||||||
include hackertray/data/hacker-tray.png
|
include hackertray/data/hacker-tray.png
|
||||||
|
exclude screenshot.png
|
105
README.md
105
README.md
@ -1,26 +1,24 @@
|
|||||||
HackerTray
|
# HackerTray
|
||||||
==========
|
|
||||||
|
|
||||||
[![HackerTray on crate.io](https://pypip.in/v/hackertray/badge.png)](https://crate.io/packages/hackertray/)
|
[![HackerTray on PyPi](https://pypip.in/v/hackertray/badge.png)](https://pypi.python.org/pypi/hackertray/)
|
||||||
[![HackerTray on PyPi](https://pypip.in/d/hackertray/badge.png)](https://pypi.python.org/pypi/hackertray/)
|
[![HackerTray on PyPi](https://pypip.in/d/hackertray/badge.png)](https://pypi.python.org/pypi/hackertray/)
|
||||||
|
[![Build Status](https://travis-ci.org/captn3m0/hackertray.png)](https://travis-ci.org/captn3m0/hackertray) [![Coverage Status](https://coveralls.io/repos/github/captn3m0/hackertray/badge.svg?branch=master)](https://coveralls.io/github/captn3m0/hackertray?branch=master)
|
||||||
|
|
||||||
HackerTray is a simple [Hacker News](https://news.ycombinator.com/) Linux application
|
HackerTray is a simple [Hacker News](https://news.ycombinator.com/) Linux application
|
||||||
that lets you view top HN stories in your System Tray. It relies on appindicator, so
|
that lets you view top HN stories in your System Tray. It uses appindicator where available,
|
||||||
it is not guaranteed to work on all systems. It also provides a Gtk StatusIcon fallback
|
but provides a Gtk StatusIcon fallback in case AppIndicator is not available.
|
||||||
in case AppIndicator is not available.
|
|
||||||
|
|
||||||
The inspiration for this came from [Hacker Bar](http://hackerbarapp.com), which is
|
The inspiration for this came from [Hacker Bar](http://hackerbarapp.com), which is Mac-only.
|
||||||
Mac-only. I tried to port it to `node-webkit`, but had to do it in Python instead
|
|
||||||
because nw doesn't support AppIndicators yet.
|
|
||||||
|
|
||||||
##Screenshot
|
## Screenshot
|
||||||
|
|
||||||
![HackerTray Screenshot in elementaryOS](http://i.imgur.com/63l3qXV.png)
|
![HackerTray Screenshot in elementaryOS](http://i.imgur.com/63l3qXV.png)
|
||||||
|
|
||||||
##Installation
|
## Installation
|
||||||
|
|
||||||
HackerTray is distributed as a python package. Do the following to install:
|
HackerTray is distributed as a python package. Do the following to install:
|
||||||
|
|
||||||
``` sh
|
```sh
|
||||||
sudo pip install hackertray
|
sudo pip install hackertray
|
||||||
OR
|
OR
|
||||||
sudo easy_install hackertray
|
sudo easy_install hackertray
|
||||||
@ -34,42 +32,89 @@ now add it to your OS dependent session autostart method. In Ubuntu, you can
|
|||||||
access it via:
|
access it via:
|
||||||
|
|
||||||
1. System > Preferences > Sessions
|
1. System > Preferences > Sessions
|
||||||
(OR)
|
(OR)
|
||||||
2. System > Preferences > Startup Applications
|
2. System > Preferences > Startup Applications
|
||||||
|
|
||||||
depending on your Ubuntu Version. Or put it in `~/.config/openbox/autostart`
|
depending on your Ubuntu Version. Or put it in `~/.config/openbox/autostart`
|
||||||
if you are running OpenBox. [Here](http://imgur.com/mnhIzDK) is how the
|
if you are running OpenBox. [Here](http://imgur.com/mnhIzDK) is how the
|
||||||
configuration should look like in Ubuntu and its derivatives.
|
configuration should look like in Ubuntu and its derivatives.
|
||||||
|
|
||||||
###Upgrade
|
### Upgrade
|
||||||
The latest stable version is always the one [available on pip](https://pypi.python.org/pypi/hackertray/).
|
|
||||||
You can check which version you have installed with `pip freeze | grep hackertray`.
|
The latest stable version is [![the one on PyPi](https://pypip.in/v/hackertray/badge.png)](https://pypi.python.org/pypi/hackertray/)
|
||||||
|
|
||||||
|
You can check which version you have installed with `hackertray --version`.
|
||||||
|
|
||||||
To upgrade, run `pip install -U hackertray`. In some cases (Ubuntu), you might
|
To upgrade, run `pip install -U hackertray`. In some cases (Ubuntu), you might
|
||||||
need to clear the pip cache before upgrading:
|
need to clear the pip cache before upgrading:
|
||||||
|
|
||||||
`sudo rm -rf /tmp/pip-build-root/hackertray`
|
`sudo rm -rf /tmp/pip-build-root/hackertray`
|
||||||
|
|
||||||
##Features
|
HackerTray will automatically check the latest version on startup, and inform you if there is an update available.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
HackerTray accepts its various options via the command line. Run `hackertray -h` to see all options. Currently the following switches are supported:
|
||||||
|
|
||||||
|
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. Pass `auto` as PROFILE-PATH to automatically read the default profile and use that.
|
||||||
|
4. `--reverse` (or `-r`). Switches the order for the elements in the menu, so Quit is at top. Use this if your system bar is at the bottom of the screen.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### Google Chrome Profile Path
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
- [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/`).
|
||||||
|
|
||||||
|
### 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. Alternatively, just pass `auto` and HackerTray will pick the default profile automatically.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
1. Minimalist Approach to HN
|
1. Minimalist Approach to HN
|
||||||
2. Opens links in your default browser
|
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
|
4. Shows Points/Comment count in a simple format
|
||||||
|
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)
|
||||||
|
|
||||||
##To-Do
|
### Troubleshooting
|
||||||
- Auto Start
|
|
||||||
- Try to convert right click to comments link
|
|
||||||
|
|
||||||
##Author Information
|
If the app indicator fails to show in Ubuntu versions, consider installing
|
||||||
- Abhay Rana (<me@captnemo.in>)
|
python-appindicator with
|
||||||
|
|
||||||
## Donating
|
`sudo apt-get install python-appindicator`
|
||||||
Support this project and [others by captn3m0][gittip] via [gittip][].
|
|
||||||
|
|
||||||
[![Support via Gittip][gittip-badge]][gittip]
|
Note that appindicator is no longer supported in non-Ubuntu distros, because it only works on Python2.
|
||||||
|
|
||||||
[gittip-badge]: https://rawgithub.com/twolfson/gittip-badge/master/dist/gittip.png
|
### Development
|
||||||
[gittip]: https://www.gittip.com/captn3m0/
|
|
||||||
|
|
||||||
##Licence
|
To develop on hackertray, or to test out experimental versions, do the following:
|
||||||
Licenced under the MIT Licence
|
|
||||||
|
- Clone the project
|
||||||
|
- Run `(sudo) python setup.py develop` in the hackertray root directory
|
||||||
|
- Run `hackertray` with the required command line options from anywhere.
|
||||||
|
|
||||||
|
## 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 (Version `< 4.0.0`).
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
Licenced under the [MIT Licence](https://nemo.mit-license.org/). See the LICENSE file for complete license text.
|
||||||
|
@ -1,134 +1,230 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import pygtk
|
|
||||||
pygtk.require('2.0')
|
|
||||||
import gtk
|
|
||||||
|
|
||||||
|
import os
|
||||||
import requests
|
import requests
|
||||||
import webbrowser
|
import subprocess
|
||||||
|
|
||||||
|
if(os.environ.get('TRAVIS') != 'true'):
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk,GLib
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
try:
|
||||||
|
import appindicator
|
||||||
|
except ImportError:
|
||||||
|
from . import appindicator_replacement as appindicator
|
||||||
|
|
||||||
|
from .appindicator_replacement import get_icon_filename
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
from os.path import expanduser
|
from os.path import expanduser
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
try:
|
from .hackernews import HackerNews
|
||||||
import appindicator
|
from .chrome import Chrome
|
||||||
except ImportError:
|
from .firefox import Firefox
|
||||||
import appindicator_replacement as appindicator
|
from .version import Version
|
||||||
|
|
||||||
##This is to get --version to work
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
__version = pkg_resources.require("hackertray")[0].version
|
|
||||||
except ImportError, e:
|
|
||||||
__version = "Can't read version number."
|
|
||||||
|
|
||||||
from hackernews import HackerNews
|
|
||||||
|
|
||||||
class HackerNewsApp:
|
class HackerNewsApp:
|
||||||
def __init__(self):
|
HN_URL_PREFIX = "https://news.ycombinator.com/item?id="
|
||||||
#Load the database
|
UPDATE_URL = "https://github.com/captn3m0/hackertray#upgrade"
|
||||||
|
ABOUT_URL = "https://github.com/captn3m0/hackertray"
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
# Load the database
|
||||||
home = expanduser("~")
|
home = expanduser("~")
|
||||||
with open(home+'/.hackertray.json', 'a+') as content_file:
|
with open(home + '/.hackertray.json', 'a+') as content_file:
|
||||||
content_file.seek(0)
|
content_file.seek(0)
|
||||||
content = content_file.read()
|
content = content_file.read()
|
||||||
try:
|
try:
|
||||||
self.db = set(json.loads(content))
|
self.db = set(json.loads(content))
|
||||||
except:
|
except ValueError:
|
||||||
self.db = set()
|
self.db = set()
|
||||||
|
|
||||||
# create an indicator applet
|
# create an indicator applet
|
||||||
self.ind = appindicator.Indicator ("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS)
|
self.ind = appindicator.Indicator("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS)
|
||||||
self.ind.set_status (appindicator.STATUS_ACTIVE)
|
self.ind.set_status(appindicator.STATUS_ACTIVE)
|
||||||
self.ind.set_label("Y")
|
self.ind.set_icon(get_icon_filename("hacker-tray.png"))
|
||||||
|
|
||||||
# create a menu
|
# 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
|
||||||
|
self.reverse = args.reverse
|
||||||
|
|
||||||
# create items for the menu - refresh, quit and a separator
|
# create items for the menu - refresh, quit and a separator
|
||||||
menuSeparator = gtk.SeparatorMenuItem()
|
menuSeparator = Gtk.SeparatorMenuItem()
|
||||||
menuSeparator.show()
|
menuSeparator.show()
|
||||||
self.menu.append(menuSeparator)
|
self.add(menuSeparator)
|
||||||
|
|
||||||
btnAbout = gtk.MenuItem("About")
|
btnComments = Gtk.CheckMenuItem("Show Comments")
|
||||||
|
btnComments.show()
|
||||||
|
btnComments.set_active(args.comments)
|
||||||
|
btnComments.set_draw_as_radio(True)
|
||||||
|
btnComments.connect("activate", self.toggleComments)
|
||||||
|
self.add(btnComments)
|
||||||
|
|
||||||
|
btnAbout = Gtk.MenuItem("About")
|
||||||
btnAbout.show()
|
btnAbout.show()
|
||||||
btnAbout.connect("activate", self.showAbout)
|
btnAbout.connect("activate", self.showAbout)
|
||||||
self.menu.append(btnAbout)
|
self.add(btnAbout)
|
||||||
|
|
||||||
btnRefresh = gtk.MenuItem("Refresh")
|
btnRefresh = Gtk.MenuItem("Refresh")
|
||||||
btnRefresh.show()
|
btnRefresh.show()
|
||||||
btnRefresh.connect("activate", self.refresh, True) #the last parameter is for not running the timer
|
# the last parameter is for not running the timer
|
||||||
self.menu.append(btnRefresh)
|
btnRefresh.connect("activate", self.refresh, True, args.chrome)
|
||||||
|
self.add(btnRefresh)
|
||||||
|
|
||||||
btnQuit = gtk.MenuItem("Quit")
|
if Version.new_available():
|
||||||
|
btnUpdate = Gtk.MenuItem("New Update Available")
|
||||||
|
btnUpdate.show()
|
||||||
|
btnUpdate.connect('activate', self.showUpdate)
|
||||||
|
self.add(btnUpdate)
|
||||||
|
|
||||||
|
btnQuit = Gtk.MenuItem("Quit")
|
||||||
btnQuit.show()
|
btnQuit.show()
|
||||||
btnQuit.connect("activate", self.quit)
|
btnQuit.connect("activate", self.quit)
|
||||||
self.menu.append(btnQuit)
|
self.add(btnQuit)
|
||||||
|
|
||||||
self.menu.show()
|
self.menu.show()
|
||||||
|
|
||||||
self.ind.set_menu(self.menu)
|
self.ind.set_menu(self.menu)
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
'''Handle the about btn'''
|
if args.firefox == "auto":
|
||||||
|
args.firefox = Firefox.default_firefox_profile_path()
|
||||||
|
self.refresh(chrome_data_directory=args.chrome, firefox_data_directory=args.firefox)
|
||||||
|
|
||||||
|
def add(self, item):
|
||||||
|
if self.reverse:
|
||||||
|
self.menu.prepend(item)
|
||||||
|
else:
|
||||||
|
self.menu.append(item)
|
||||||
|
|
||||||
|
def toggleComments(self, widget):
|
||||||
|
"""Whether comments page is opened or not"""
|
||||||
|
self.commentState = not self.commentState
|
||||||
|
|
||||||
|
def showUpdate(self, widget):
|
||||||
|
"""Handle the update button"""
|
||||||
|
webbrowser.open(HackerNewsApp.UPDATE_URL)
|
||||||
|
# Remove the update button once clicked
|
||||||
|
self.menu.remove(widget)
|
||||||
|
|
||||||
def showAbout(self, widget):
|
def showAbout(self, widget):
|
||||||
webbrowser.open("https://github.com/captn3m0/hackertray/")
|
"""Handle the about btn"""
|
||||||
|
webbrowser.open(HackerNewsApp.ABOUT_URL)
|
||||||
|
|
||||||
''' Handler for the quit button'''
|
# ToDo: Handle keyboard interrupt properly
|
||||||
#ToDo: Handle keyboard interrupt properly
|
|
||||||
def quit(self, widget, data=None):
|
def quit(self, widget, data=None):
|
||||||
l=list(self.db)
|
""" Handler for the quit button"""
|
||||||
|
l = list(self.db)
|
||||||
home = expanduser("~")
|
home = expanduser("~")
|
||||||
#truncate the file
|
|
||||||
with open(home+'/.hackertray.json', 'w+') as file:
|
# truncate the file
|
||||||
|
with open(home + '/.hackertray.json', 'w+') as file:
|
||||||
file.write(json.dumps(l))
|
file.write(json.dumps(l))
|
||||||
gtk.main_quit()
|
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
signal.signal(signal.SIGINT, self.quit)
|
signal.signal(signal.SIGINT, self.quit)
|
||||||
gtk.main()
|
Gtk.main()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
'''Opens the link in the web browser'''
|
def open(self, widget, **args):
|
||||||
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
|
# 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
|
# to set it to active and we don't want the signal to be processed
|
||||||
if(widget.get_active() == False):
|
if not widget.get_active():
|
||||||
widget.disconnect(widget.signal_id)
|
widget.disconnect(widget.signal_id)
|
||||||
widget.set_active(True)
|
widget.set_active(True)
|
||||||
widget.signal_id = widget.connect('activate', self.open)
|
widget.signal_id = widget.connect('activate', self.open)
|
||||||
|
|
||||||
self.db.add(widget.item_id)
|
self.db.add(widget.item_id)
|
||||||
webbrowser.open(widget.url)
|
webbrowser.open(widget.url)
|
||||||
|
|
||||||
'''Adds an item to the menu'''
|
# TODO: Add support for Shift+Click or Right Click
|
||||||
|
# to do the opposite of the current commentState setting
|
||||||
|
if self.commentState:
|
||||||
|
webbrowser.open(self.HN_URL_PREFIX + str(widget.hn_id))
|
||||||
|
|
||||||
def addItem(self, item):
|
def addItem(self, item):
|
||||||
if(item['points'] == 0 or item['points'] == None): #This is in the case of YC Job Postings, which we skip
|
"""Adds an item to the menu"""
|
||||||
|
# This is in the case of YC Job Postings, which we skip
|
||||||
|
if item['points'] == 0 or item['points'] is None:
|
||||||
return
|
return
|
||||||
i = gtk.CheckMenuItem("("+str(item['points']).zfill(3)+"/"+str(item['comments_count']).zfill(3)+") "+item['title'])
|
|
||||||
i.set_active(item['id'] in self.db)
|
points = str(item['points']).zfill(3) + "/" + str(item['comments_count']).zfill(3)
|
||||||
|
|
||||||
|
i = Gtk.CheckMenuItem.new_with_label(label="(" + points + ")"+item['title'])
|
||||||
|
label = i.get_child()
|
||||||
|
label.set_markup("<tt>" + points + "</tt> <span>"+item['title']+"</span>".format(points=points, title=item['title']))
|
||||||
|
label.set_selectable(False)
|
||||||
|
|
||||||
|
visited = item['history'] or item['id'] in self.db
|
||||||
|
|
||||||
|
i.set_active(visited)
|
||||||
i.url = item['url']
|
i.url = item['url']
|
||||||
|
tooltip = "{url}\nPosted by {user} {timeago}".format(url=item['url'], user=item['user'], timeago=item['time_ago'])
|
||||||
|
i.set_tooltip_text(tooltip)
|
||||||
i.signal_id = i.connect('activate', self.open)
|
i.signal_id = i.connect('activate', self.open)
|
||||||
|
i.hn_id = item['id']
|
||||||
i.item_id = item['id']
|
i.item_id = item['id']
|
||||||
|
if self.reverse:
|
||||||
|
self.menu.append(i)
|
||||||
|
else:
|
||||||
self.menu.prepend(i)
|
self.menu.prepend(i)
|
||||||
i.show()
|
i.show()
|
||||||
|
|
||||||
'''Refreshes the menu '''
|
def refresh(self, widget=None, no_timer=False, chrome_data_directory=None, firefox_data_directory=None):
|
||||||
def refresh(self, widget=None, no_timer=False):
|
"""Refreshes the menu """
|
||||||
data = reversed(HackerNews.getHomePage()[0:20]);
|
try:
|
||||||
#Remove all the current stories
|
# Create an array of 20 false to denote matches in History
|
||||||
|
searchResults = [False]*20
|
||||||
|
data = list(reversed(HackerNews.getHomePage()[0:20]))
|
||||||
|
urls = [item['url'] for item in data]
|
||||||
|
if(chrome_data_directory):
|
||||||
|
searchResults = self.mergeBoolArray(searchResults, Chrome.search(urls, chrome_data_directory))
|
||||||
|
|
||||||
|
if(firefox_data_directory):
|
||||||
|
searchResults = self.mergeBoolArray(searchResults, Firefox.search(urls, firefox_data_directory))
|
||||||
|
|
||||||
|
# Remove all the current stories
|
||||||
for i in self.menu.get_children():
|
for i in self.menu.get_children():
|
||||||
if(hasattr(i,'url')):
|
if hasattr(i, 'url'):
|
||||||
self.menu.remove(i)
|
self.menu.remove(i)
|
||||||
#Add back all the refreshed news
|
|
||||||
for i in data:
|
# Add back all the refreshed news
|
||||||
self.addItem(i)
|
for index, item in enumerate(data):
|
||||||
#Call every 5 minutes
|
item['history'] = searchResults[index]
|
||||||
if no_timer==False:
|
if item['url'].startswith('item?id='):
|
||||||
gtk.timeout_add(10*60*1000, self.refresh)
|
item['url'] = "https://news.ycombinator.com/" + item['url']
|
||||||
|
|
||||||
|
self.addItem(item)
|
||||||
|
# Catch network errors
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print("[+] There was an error in fetching news items")
|
||||||
|
finally:
|
||||||
|
# Call every 10 minutes
|
||||||
|
if not no_timer:
|
||||||
|
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):
|
||||||
|
for index, var in enumerate(original):
|
||||||
|
original[index] = original[index] or patch[index]
|
||||||
|
return original
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Hacker News in your System Tray')
|
parser = argparse.ArgumentParser(description='Hacker News in your System Tray')
|
||||||
parser.add_argument('--version', action='version', version=__version)
|
parser.add_argument('-v', '--version', action='version', version=Version.current())
|
||||||
parser.parse_args()
|
parser.add_argument('-c', '--comments', dest='comments', default=False, action='store_true', help="Load the HN comments link for the article as well")
|
||||||
indicator = HackerNewsApp()
|
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. Pass auto to automatically pick the default profile")
|
||||||
|
parser.add_argument('-r', '--reverse', dest='reverse', default=False, action='store_true', help="Reverse the order of items. Use if your status bar is at the bottom of the screen")
|
||||||
|
args = parser.parse_args()
|
||||||
|
indicator = HackerNewsApp(args)
|
||||||
indicator.run()
|
indicator.run()
|
||||||
|
@ -9,12 +9,10 @@
|
|||||||
#=========================
|
#=========================
|
||||||
|
|
||||||
# We require PyGTK
|
# We require PyGTK
|
||||||
import gtk
|
from gi.repository import Gtk,GLib
|
||||||
import gobject
|
|
||||||
|
|
||||||
# We also need os and sys
|
# We also need os and sys
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from pkg_resources import resource_filename
|
from pkg_resources import resource_filename
|
||||||
|
|
||||||
@ -27,24 +25,23 @@ STATUS_ATTENTION = 1
|
|||||||
|
|
||||||
# Locations to search for the given icon
|
# Locations to search for the given icon
|
||||||
|
|
||||||
def get_icon_filename(icon_name):
|
|
||||||
|
|
||||||
|
def get_icon_filename(icon_name):
|
||||||
# Determine where the icon is
|
# Determine where the icon is
|
||||||
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png'))
|
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png'))
|
||||||
|
|
||||||
|
|
||||||
# The main class
|
# The main class
|
||||||
class Indicator:
|
class Indicator:
|
||||||
|
|
||||||
# Constructor
|
# Constructor
|
||||||
|
|
||||||
def __init__ (self,unknown,icon,category):
|
def __init__(self, unknown, icon, category):
|
||||||
|
|
||||||
# Store the settings
|
# Store the settings
|
||||||
self.inactive_icon = get_icon_filename(icon)
|
self.inactive_icon = get_icon_filename(icon)
|
||||||
self.active_icon = "" # Blank until the user calls set_attention_icon
|
self.active_icon = "" # Blank until the user calls set_attention_icon
|
||||||
|
|
||||||
# Create the status icon
|
# Create the status icon
|
||||||
self.icon = gtk.StatusIcon()
|
self.icon = Gtk.StatusIcon()
|
||||||
|
|
||||||
# Initialize to the default icon
|
# Initialize to the default icon
|
||||||
self.icon.set_from_file(self.inactive_icon)
|
self.icon.set_from_file(self.inactive_icon)
|
||||||
@ -52,8 +49,7 @@ class Indicator:
|
|||||||
# Set the rest of the vars
|
# Set the rest of the vars
|
||||||
self.menu = None # We have no menu yet
|
self.menu = None # We have no menu yet
|
||||||
|
|
||||||
def set_menu(self,menu):
|
def set_menu(self, menu):
|
||||||
|
|
||||||
# Save a copy of the menu
|
# Save a copy of the menu
|
||||||
self.menu = menu
|
self.menu = menu
|
||||||
|
|
||||||
@ -63,7 +59,6 @@ class Indicator:
|
|||||||
self.icon.connect("activate", self.show_menu)
|
self.icon.connect("activate", self.show_menu)
|
||||||
|
|
||||||
def set_status(self, status):
|
def set_status(self, status):
|
||||||
|
|
||||||
# Status defines whether the active or inactive
|
# Status defines whether the active or inactive
|
||||||
# icon should be displayed.
|
# icon should be displayed.
|
||||||
if status == STATUS_ACTIVE:
|
if status == STATUS_ACTIVE:
|
||||||
@ -76,46 +71,16 @@ class Indicator:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def set_icon(self, icon):
|
def set_icon(self, icon):
|
||||||
|
|
||||||
# Set the new icon
|
# Set the new icon
|
||||||
self.icon.set_from_file(get_icon_filename(icon))
|
self.icon.set_from_file(get_icon_filename(icon))
|
||||||
|
|
||||||
def set_attention_icon(self, icon):
|
def set_attention_icon(self, icon):
|
||||||
|
|
||||||
# Set the icon filename as the attention icon
|
# Set the icon filename as the attention icon
|
||||||
self.active_icon = get_icon_filename(icon)
|
self.active_icon = get_icon_filename(icon)
|
||||||
|
|
||||||
def show_menu(self, widget):
|
def show_menu(self, widget):
|
||||||
|
|
||||||
# Show the menu
|
# 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()
|
|
||||||
|
|
||||||
self.x = mouse_rect.x
|
|
||||||
self.y = mouse_rect.y
|
|
||||||
self.right = self.x + mouse_rect.width
|
|
||||||
self.bottom = self.y + mouse_rect.height
|
|
||||||
|
|
||||||
# Set a timer to poll the menu
|
|
||||||
self.timer = gobject.timeout_add(100, self.check_mouse)
|
|
||||||
|
|
||||||
def check_mouse(self):
|
|
||||||
|
|
||||||
if not self.menu.get_window().is_visible():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Now check the global mouse coords
|
|
||||||
root = self.menu.get_screen().get_root_window()
|
|
||||||
|
|
||||||
x,y,z = root.get_pointer()
|
|
||||||
|
|
||||||
if x < self.x or x > self.right or y < self.y or y > self.bottom:
|
|
||||||
self.hide_menu()
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def hide_menu(self):
|
def hide_menu(self):
|
||||||
|
|
||||||
self.menu.popdown()
|
self.menu.popdown()
|
||||||
|
32
hackertray/chrome.py
Normal file
32
hackertray/chrome.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
import sqlite3
|
||||||
|
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)
|
||||||
|
conn = sqlite3.connect(Chrome.HISTORY_TMP_LOCATION)
|
||||||
|
db = conn.cursor()
|
||||||
|
result = []
|
||||||
|
for url in urls:
|
||||||
|
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')
|
||||||
|
if not os.path.isfile(file_name):
|
||||||
|
print("ERROR: ", "Could not find Chrome history file", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
shutil.copyfile(file_name, Chrome.HISTORY_TMP_LOCATION)
|
Binary file not shown.
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 475 B |
51
hackertray/firefox.py
Normal file
51
hackertray/firefox.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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 = str(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.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"]
|
||||||
|
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)
|
||||||
|
conn = sqlite3.connect(Firefox.HISTORY_TMP_LOCATION)
|
||||||
|
db = conn.cursor()
|
||||||
|
result = []
|
||||||
|
for url in urls:
|
||||||
|
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, using %s" % file_name)
|
||||||
|
sys.exit(1)
|
||||||
|
shutil.copyfile(file_name, Firefox.HISTORY_TMP_LOCATION)
|
@ -3,15 +3,17 @@ import requests
|
|||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
'https://node-hnapi.herokuapp.com/'
|
'https://node-hnapi.herokuapp.com/'
|
||||||
];
|
]
|
||||||
|
|
||||||
|
|
||||||
class HackerNews:
|
class HackerNews:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getHomePage():
|
def getHomePage():
|
||||||
random.shuffle(urls)
|
random.shuffle(urls)
|
||||||
for i in urls:
|
for i in urls:
|
||||||
r = requests.get(i+"news")
|
r = requests.get(i + "news")
|
||||||
try:
|
try:
|
||||||
return r.json()
|
return r.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue;
|
continue
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from hackernews import HackerNews
|
|
||||||
|
|
||||||
class HNTest(unittest.TestCase):
|
|
||||||
def runTest(self):
|
|
||||||
data = HackerNews.getHomePage()
|
|
||||||
self.assertTrue(len(data)>0)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
29
hackertray/version.py
Normal file
29
hackertray/version.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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()
|
||||||
|
return res['info']['version']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def current():
|
||||||
|
return pkg_resources.require("hackertray")[0].version
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_available():
|
||||||
|
latest = Version.latest()
|
||||||
|
current = Version.current()
|
||||||
|
try:
|
||||||
|
if pkg_resources.parse_version(latest) > pkg_resources.parse_version(current):
|
||||||
|
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")
|
||||||
|
return False
|
19
setup.py
19
setup.py
@ -1,26 +1,25 @@
|
|||||||
from setuptools import setup
|
|
||||||
from setuptools import find_packages
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools import find_packages
|
||||||
|
|
||||||
requirements = ['requests']
|
requirements = ['requests']
|
||||||
if sys.version_info < (2, 7):
|
|
||||||
requirements.append('argparse')
|
|
||||||
|
|
||||||
setup(name='hackertray',
|
setup(name='hackertray',
|
||||||
version='1.9',
|
version='4.0.2',
|
||||||
description='Hacker News app that sits in your System Tray',
|
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',
|
keywords='hacker news hn tray system tray icon hackertray',
|
||||||
url='http://captnemo.in/hackertray',
|
url='https://captnemo.in/hackertray',
|
||||||
author='Abhay Rana',
|
author='Abhay Rana (Nemo)',
|
||||||
author_email='me@captnemo.in',
|
author_email='me@captnemo.in',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
package_data={
|
package_data={
|
||||||
'hackertray.data':['hacker-tray.png']
|
'hackertray.data': ['hacker-tray.png']
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'requests',
|
'requests>=2.23.0'
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['hackertray = hackertray:main'],
|
'console_scripts': ['hackertray = hackertray:main'],
|
||||||
|
BIN
test/History
Normal file
BIN
test/History
Normal file
Binary file not shown.
15
test/chrome_test.py
Normal file
15
test/chrome_test.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from hackertray import Chrome
|
||||||
|
|
||||||
|
class ChromeTest(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
config_folder_path = os.getcwd()+'/test/'
|
||||||
|
data = Chrome.search([
|
||||||
|
"https://github.com/",
|
||||||
|
"https://news.ycombinator.com/",
|
||||||
|
"https://github.com/captn3m0/hackertray",
|
||||||
|
"http://invalid_url/"],
|
||||||
|
config_folder_path)
|
||||||
|
self.assertTrue(data == [True,True,True,False])
|
32
test/firefox_test.py
Normal file
32
test/firefox_test.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from hackertray import Firefox
|
||||||
|
|
||||||
|
class FirefoxTest(unittest.TestCase):
|
||||||
|
def test_history(self):
|
||||||
|
config_folder_path = os.getcwd()+'/test/'
|
||||||
|
data = Firefox.search([
|
||||||
|
"http://www.hckrnews.com/",
|
||||||
|
"http://www.google.com/",
|
||||||
|
"http://wiki.ubuntu.com/",
|
||||||
|
"http://invalid_url/"],
|
||||||
|
config_folder_path)
|
||||||
|
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(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]
|
||||||
|
Name=default
|
||||||
|
IsRelative=1
|
||||||
|
Path=x0ran0o9.default
|
||||||
|
Default=1
|
||||||
|
""")
|
||||||
|
self.assertTrue(Firefox.default_firefox_profile_path()==str(Path.home().joinpath(".mozilla/firefox/x0ran0o9.default")))
|
11
test/hn_test.py
Normal file
11
test/hn_test.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import unittest
|
||||||
|
from hackertray import HackerNews
|
||||||
|
|
||||||
|
class HNTest(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
data = HackerNews.getHomePage()
|
||||||
|
self.assertTrue(len(data) > 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
BIN
test/places.sqlite
Normal file
BIN
test/places.sqlite
Normal file
Binary file not shown.
10
test/version_test.py
Normal file
10
test/version_test.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import unittest
|
||||||
|
from hackertray import Version
|
||||||
|
|
||||||
|
class VersionTest(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
version = Version.latest()
|
||||||
|
assert version
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user