├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── build.py
├── build.sh
├── certifi
├── __init__.py
├── __main__.py
├── cacert.pem
├── core.py
├── old_root.pem
└── weak.pem
├── classify.py
├── elasticsearch
├── elasticsearch.keystore
├── elasticsearch.yml
└── jvm.options
├── extension
├── background.js
├── contentscript.js
├── ikke-1.4.zip
├── ikke.css
├── jquery-addons.js
├── jquery-ui.min.css
├── jquery-ui.min.js
├── jquery.js
├── manifest.json
├── octopus.jpg
└── settings.js
├── facebooksdk
├── __init__.py
└── version.py
├── graph.py
├── hosts.py
├── html
├── 3rd
│ ├── d3.v4.js
│ ├── d3.v4.min.js
│ ├── jquery-3.2.1.min.js
│ ├── jquery-ui-1.12.1
│ │ ├── AUTHORS.txt
│ │ ├── LICENSE.txt
│ │ ├── external
│ │ │ └── jquery
│ │ │ │ └── jquery.js
│ │ ├── images
│ │ │ ├── ui-icons_444444_256x240.png
│ │ │ ├── ui-icons_555555_256x240.png
│ │ │ ├── ui-icons_777620_256x240.png
│ │ │ ├── ui-icons_777777_256x240.png
│ │ │ ├── ui-icons_cc0000_256x240.png
│ │ │ └── ui-icons_ffffff_256x240.png
│ │ ├── index.html
│ │ ├── jquery-ui.css
│ │ ├── jquery-ui.js
│ │ ├── jquery-ui.min.css
│ │ ├── jquery-ui.min.js
│ │ ├── jquery-ui.structure.css
│ │ ├── jquery-ui.structure.min.css
│ │ ├── jquery-ui.theme.css
│ │ ├── jquery-ui.theme.min.css
│ │ └── package.json
│ ├── jstree-3.2.1.min.css
│ └── jstree-3.2.1.min.js
├── extensions.html
├── graph.csv
├── graph.json
├── icons
│ ├── blue-circle.png
│ ├── browser-web-icon.png
│ ├── calendar-icon.png
│ ├── delete_icon.png
│ ├── excel-xls-icon.png
│ ├── facebook.png
│ ├── favicon.ico
│ ├── file-icon.png
│ ├── git-icon.png
│ ├── gmail-icon.png
│ ├── hangouts-icon.png
│ ├── keynote-icon.png
│ ├── loading_spinner.gif
│ ├── orange-circle.png
│ ├── pdf-icon.png
│ ├── person-icon.png
│ ├── ppt-icon.png
│ ├── rainbow-circle.png
│ ├── rtf-icon.png
│ ├── sad-computer.png
│ ├── text-icon.png
│ ├── tiff-icon.png
│ ├── white_pixel.png
│ └── word-doc-icon.png
├── index.html
├── main.css
├── main.js
├── projects.js
├── settings.css
├── settings.html
└── settings.js
├── htmlparser.py
├── images
├── architecture.png
├── screenshot-context-menu.png
├── screenshot-grid.png
├── screenshot-ikke-dot.png
├── screenshot-ikke-graph.png
├── screenshot-ikke-related.png
├── screenshot-ikke-settings.png
└── screenshot-ikke-statusbar.png
├── importers
├── __init__.py
├── browser.py
├── calendar.py
├── contact.py
├── download.py
├── file.py
├── git.py
├── gmail.py
├── gmail_credentials.json
├── google_apis.py
├── hangouts.py
└── quickstart.py
├── installation
└── ikke.plist
├── installer.py
├── localsearch
└── search.vbs
├── main.py
├── memory.py
├── poller.py
├── preferences.py
├── pubsub.py
├── pyinstaller.spec
├── server.py
├── server.spec
├── settings.py
├── setup.py
├── simple_logging.py
├── stopwords.py
├── storage.py
├── threadpool.py
├── utils.py
└── words.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/*.pyc
2 | *.pyc
3 | token.pickle
4 | .vscode/.ropeproject/config.py
5 | .vscode/.ropeproject/objectdb
6 | venv/*
7 | build/*
8 | dist/*
9 | .eggs/*
10 | exe.egg-info/*
11 | install.egg-info/*
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.pylintEnabled": true,
3 | "python.linting.enabled": true
4 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | NON-COMMERCIAL IKKE License
2 |
3 | Copyright (c) 2017-2021 Chris Laffra
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to use,
7 | copy, and modify the software for Personal Use.
8 |
9 | This license is subject to the following conditions:
10 |
11 | Without limiting other conditions in the License, the grant of rights under
12 | the License will not include, and the License does not grant to you, the
13 | right to Sell the Software.
14 |
15 | For purposes of the foregoing, “Sell” means practicing any or all of the
16 | rights granted to you under the License to provide to third parties, for
17 | a fee or other consideration (including without limitation fees for
18 | hosting or consulting/ support services related to the Software), a product
19 | or service whose value derives, entirely or substantially, from the
20 | functionality of the Software.
21 |
22 | The above copyright notice and this permission notice shall be included in all
23 | copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ikke
2 | Easily find back all your data!
3 |
4 | # Features
5 |
6 | Ikke produces a local backup of your (social) data, with fast local search,
7 | visualizing the connections between de data across various sources.
8 |
9 | Why the name Ikke?
10 |
11 | Ikke is a Dutch colloquial/slang word, meaning "I", "Me", or "Myself". It is pronounced as "ik-kuh", and is mainly used in the Dutch pseudo-narcist phrase "Ikke, ikke, ikke, en de rest kan stikken", translated roughly into "Me, me, me, and screw all others".
12 |
13 | When [pronounced correctly](https://upload.wikimedia.org/wikipedia/commons/3/39/Nl-ikke.ogg),
14 | Ikke sounds a lot like the English word "ticket", if you remove the leading and trailing t's.
15 |
16 | # How it works
17 |
18 |
19 |
20 | |
21 |
22 | Ikke consists of various components:
23 | * Importers for data sources such as browser history, local downloads, gmail, git, etc.
24 | * A Chrome browser extension that:
25 | * collects a thumbnail image of websites you visited to find them back quickly.
26 | * shows related items to the current "Essence" of the site being viewed, see the yellow Dot in web pages.
27 | * A statusbar icon showing the number of related items with a menu.
28 | * A super-fast freetext search backend that runs on your local machine, not needing any cloud access.
29 | * Smooth graph visualizations that show relationships between various data sources.
30 | * A [Settings UI](http://localhost:1964/settings) to control the index and indicate if you want to use the Dot or not.
31 |
32 | # Installation
33 |
34 | Setup Ikke:
35 | * Clone the repo and cd to its root
36 | ```
37 | git clone https://github.com/laffra/Ikke
38 | cd Ikke
39 | ```
40 | * Set up a virtualenv
41 | ```
42 | python3 -m pip install virtualenv
43 | python3 -m venv env_ikke
44 | ```
45 | * Activate the virtualenv you just created
46 | ```
47 | source env_ikke/bin/activate
48 | ```
49 | * Install elasticsearch and the python dependencies:
50 | ```
51 | python3 setup.py install
52 | ```
53 | * Visit "chrome://extensions" in your browser
54 | * Load the unpacked extension from the repo's "extension" folder.
55 |
56 | The first time Ikke runs, it performs post-setup tasks. One of the things Ikke needs is your approval to index your Google data, such as gmail, calendar, etc. Things that happen during the first run:
57 | * A Google dialog is shown to give "Ikke Graph" access to your data.
58 | * The "Ikke graph" app is not verified by Google, so you may get a scary warning. This is OK. Click on the
59 | "advanced" link and provide access. See below what happens next.
60 | * The auth token received from Google is stored on your local machine under ~/IKKE.
61 |
62 | Your privacy is preserved:
63 | * The "Ikke Graph" app can only access your data using the locally stored token. Therefore, it can only index your data on your local machine.
64 | * The token and all your indexed data are stored locally only. No one will have access to it, unless they run a program on your local machine and use the token.
65 | * You will get an email and/or notification from Google saying "Ikke Graph was granted access to your Google Account"
66 |
67 | # Usage
68 |
69 | Run Ikke:
70 | ```
71 | python3 main.py
72 | ```
73 |
74 | Visit your settings:
75 | * Click on the statusbar number icon or see [Settings](http://localhost:1964/settings):
76 |
77 |
78 |
79 | |
80 |
81 | * By default, Ikke will index your browser history.
82 | * To load other sources, such as gmail, hit the corresponding "Load" button.
83 | * Optional: Enable the browser extension's "Dot".
84 | * Click on the IKKE logo to start a new search on the currently indexed data.
85 |
86 | # Using the Dot
87 |
88 | Use the "Dot" to show related items:
89 | * Enable the feature in your settings (see above).
90 | * Go to a website and notice the dot appear, such as the one shown below next to Elon Musk saying "12":
91 |
92 |
93 |
94 | |
95 |
96 | * Click on the dot to discover the 12 related items to Elon Musk (this list is different for every user, of course):
97 |
98 |
99 |
100 | |
101 |
102 | # Showing an Ikke Graph
103 |
104 | You can show an Ikke Graph from the Dot, the statusbar, and from its UI:
105 |
106 |
107 |
108 | |
109 |
110 | # Showing an Ikke Grid
111 |
112 | The graph is great to see a quick overview of the relationships between the data.
113 | If you want to focus more on time and recency, the Ikke Grid may be a better UI:
114 |
115 |
116 |
117 | |
118 |
119 | The Grid and the Graph show the same information, sorted differently.
120 |
121 | # Using the status bar icon
122 |
123 | You can always use the statusbar icon to explore related items as well:
124 |
125 |
126 |
127 | |
128 |
129 | # Using the context menu
130 |
131 | Inside Chrome, the Ikke Dot tries to guess the current "essence" of the page. It favors words
132 | that appear in the top-middle of the page and that have more "weight" than others.
133 | Sometimes, however, you will want to search for a word nearby. Simply right-mouse click on it,
134 | or select some text first, and activate the context menu to use the Ikke menu item:
135 |
136 |
137 |
138 | |
139 |
140 | # Uninstall
141 |
142 | Uninstall takes three steps:
143 | * Visit [Settings](http://localhost:1964/settings) and delete all data.
144 | * Remove ~/IKKE entirely
145 | * Remove the local repo you cloned during startup
146 |
147 | # Future Work
148 |
149 | Some things that could improve Ikke:
150 | * Add authentication to elasticsearch
151 | * Add an ML model to the Essence finder
152 | * Finish up the py2app bundling (see build.py)
153 | * Distribute as a Mac app in the AppStore with a real installer
154 | * Handle possible port number conflicts
155 | * Add more importers, such as TikTok, FB, IG, and WhatsApp...
156 | * Consider extensions for other browsers, such as IE, Firefox
157 | * Add a plugin to standalone tools, such as VSCode.
158 |
159 | # Privacy
160 |
161 | You privacy is key. Ikke does not upload ANY data. Everything being indexed is stored on your local machine. Ikke does not use your data for marketing purposes or ads. No logging is ever uploaded. Your data is yours and stays yours.
162 |
--------------------------------------------------------------------------------
/build.py:
--------------------------------------------------------------------------------
1 | import atexit
2 | from distutils import cmd
3 | from setuptools import setup, find_packages
4 | from setuptools.command.install import install
5 | from subprocess import check_call
6 | import py2app
7 |
8 | APP = ['main.py']
9 | DATA_FILES = []
10 | OPTIONS = {
11 | 'argv_emulation': True,
12 | 'plist': {
13 | 'LSUIElement': True,
14 | },
15 | 'packages': ['rumps'],
16 | }
17 |
18 | setup(
19 | app = APP,
20 | data_files = DATA_FILES,
21 | options = {'py2app': OPTIONS},
22 | setup_requires = ['py2app'],
23 | )
24 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | clear
2 |
3 | echo "Step 1: Stop existing Ikke instances..."
4 | killall ikke
5 |
6 | echo "Step 2: Clean the old distribution..."
7 | cd /Users/laffra/dev/Ikke && rm -rf dist build
8 |
9 | echo "Step 3: Create new distribution..."
10 | pyinstaller pyinstaller.spec
11 |
12 | echo "Step 4: Launch to test..."
13 | open dist/ikke
--------------------------------------------------------------------------------
/certifi/__init__.py:
--------------------------------------------------------------------------------
1 | from .core import where, old_where
2 |
3 | __version__ = "2017.11.05"
4 |
--------------------------------------------------------------------------------
/certifi/__main__.py:
--------------------------------------------------------------------------------
1 | from certifi import where
2 | print(where())
3 |
--------------------------------------------------------------------------------
/certifi/core.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | certifi.py
6 | ~~~~~~~~~~
7 |
8 | This module returns the installation location of cacert.pem.
9 | """
10 | import os
11 | import warnings
12 |
13 |
14 | class DeprecatedBundleWarning(DeprecationWarning):
15 | """
16 | The weak security bundle is being deprecated. Please bother your service
17 | provider to get them to stop using cross-signed roots.
18 | """
19 |
20 |
21 | def where():
22 | f = os.path.dirname(__file__)
23 |
24 | return os.path.join(f, 'cacert.pem')
25 |
26 |
27 | def old_where():
28 | warnings.warn(
29 | "The weak security bundle is being deprecated. It will be removed in "
30 | "2018.",
31 | DeprecatedBundleWarning
32 | )
33 | f = os.path.dirname(__file__)
34 | return os.path.join(f, 'weak.pem')
35 |
36 | if __name__ == '__main__':
37 | print(where())
38 |
--------------------------------------------------------------------------------
/classify.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import itertools
3 | import logging
4 | import stopwords
5 | import storage
6 |
7 | MOST_COMMON_COUNT = 41
8 | ADD_CONTENT_LABELS = False
9 |
10 |
11 | logger = logging.getLogger(__name__)
12 |
13 |
14 | def shorten(label):
15 | if len(label) > 31:
16 | label = label[:13] + ' ... ' + label[-13:]
17 | return label
18 |
19 |
20 | class Label(storage.Data):
21 | def __init__(self, name):
22 | super(Label, self).__init__(name)
23 | self.kind = 'label'
24 | self.color = '#888'
25 | self.font_size = 12
26 |
27 | def is_related_item(self, other):
28 | return self.label in other.words
29 |
30 | def is_duplicate(self, duplicates):
31 | if self.label in duplicates:
32 | return True
33 | duplicates.add(self.label)
34 | return False
35 |
36 |
37 | class TooMuch(storage.Data):
38 | def __init__(self, count):
39 | super(TooMuch, self).__init__('Search is too broad: %d results not shown.' % count)
40 | self.kind = 'label'
41 | self.color = 'red'
42 | self.font_size = 48
43 |
44 |
45 | def add_contact(contact, contacts):
46 | if contact.label in contacts:
47 | contact = contacts[contact.label]
48 | else:
49 | contacts[contact.label] = contact
50 | return contact
51 |
52 |
53 | def get_persons(items):
54 | return [(contact, item) for item in items for contact in item.persons]
55 |
56 |
57 | def adjacent(items):
58 | for index in range(len(items) - 2):
59 | yield (items[index], items[index + 1])
60 |
61 |
62 | def get_item_edges(items):
63 | # type(list, str) -> list
64 | timestamps = [item.timestamp for item in items if item.timestamp]
65 | if timestamps:
66 | storage.TimeNode.set_timerange(min(timestamps), max(timestamps))
67 | return {
68 | (item, related)
69 | for item in items
70 | for related in item.get_related_items()
71 | }
72 |
73 |
74 | def remove_duplicates(items, keep_duplicates):
75 | # type(list, bool) -> list
76 | duplicates = set()
77 | sorted_items = sorted(items, key=lambda item: -(item.timestamp or 0))
78 | results = [item for item in sorted_items if keep_duplicates or not item.is_duplicate(duplicates)]
79 | if len(results) > storage.MAX_NUMBER_OF_ITEMS:
80 | too_much = TooMuch(len(results) - storage.MAX_NUMBER_OF_ITEMS)
81 | results = results[:storage.MAX_NUMBER_OF_ITEMS]
82 | results.append(too_much)
83 | return results
84 |
85 |
86 | def get_most_common_words(query, items):
87 | counter = collections.Counter()
88 | for item in items:
89 | item.update_words(items)
90 | counter.update([
91 | word
92 | for word in item.words
93 | if word and word != query
94 | ])
95 | for word in query.lower().split(' '):
96 | if word in counter:
97 | del counter[word]
98 | most_common = {key for key, count in counter.most_common(MOST_COMMON_COUNT)}
99 | return most_common
100 |
101 |
102 |
103 | def get_edges(query, items, add_words=False, keep_duplicates=False):
104 | # type(str, list, str, bool) -> (list, list)
105 | edges = get_item_edges(items)
106 | if add_words:
107 | items += [Label(word) for word in get_most_common_words(query, items) if not stopwords.is_stopword(word)]
108 | for item1, item2 in itertools.combinations(items, 2):
109 | if item1.is_related_item(item2) or item2.is_related_item(item1):
110 | item1.edges += 1
111 | item2.edges += 1
112 | logger.debug(" - add edge %s %s %s - %s" % ( item1.is_related_item(item2), item2.is_related_item(item1), item1, item2))
113 | edges.add((item1, item2))
114 | items = remove_duplicates(items, keep_duplicates)
115 | edges = [edge for edge in edges if edge[0] in items and edge[1] in items]
116 | logger.info("Created graph for %d edges and %d items, with %d emails" % (len(edges), len(items), len(list(filter(lambda item: item.kind == "gmail", items)))))
117 | return list(edges), items
118 |
119 |
120 | def debug_results(labels, items):
121 | show_details = False
122 | level = logging.get_level()
123 | logging.set_level(logging.DEBUG)
124 | logging.debug('found %d labels with %d items' % (len(labels), len(items)))
125 | logging.debug('Included:')
126 |
127 | def shorten(x):
128 | if isinstance(x, str): return x.replace('\n', ' ')
129 | return x
130 |
131 | for item in items:
132 | logging.debug(' %s %s %s' % (item.kind, repr(item.label), item.uid))
133 | if show_details:
134 | for var in vars(item):
135 | logging.debug(' %s: %s' % (var, shorten(getattr(item, var))))
136 | for k,v in labels.items():
137 | logging.debug(k.label)
138 | for item in v:
139 | logging.debug (' %s %s' % (item.kind, item.label))
140 | logging.debug('Removed:')
141 | for item in set(all_items) - set(items):
142 | logging.debug(' %s %s %s' % (item.kind, repr(item.label), item.uid))
143 | if show_details:
144 | for var in vars(item):
145 | logging.debug(' %s: %s' % (var, shorten(getattr(item, var))))
146 | logging.set_level(level)
147 |
148 |
149 |
150 | if __name__ == '__main__':
151 | level = logging.get_level()
152 | logging.set_level(logging.DEBUG)
153 | query = 'blockchain'
154 | items = storage.Storage.search(query, 3)
155 | logging.debug('Found %d items.' % len(items))
156 | edges, all_items = get_edges(items)
157 | logging.debug('Edges:')
158 | for item1, item2 in edges:
159 | logging.debug(' %s - %s' % (repr(item1.label), repr(item2.label)))
160 | logging.debug('Items:')
161 | for item in items:
162 | logging.debug(' %s' % repr(item.label))
163 |
--------------------------------------------------------------------------------
/elasticsearch/elasticsearch.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laffra/Ikke/5c0e0a4103e66de53469233c7c4a07995ad7d3d8/elasticsearch/elasticsearch.keystore
--------------------------------------------------------------------------------
/elasticsearch/elasticsearch.yml:
--------------------------------------------------------------------------------
1 | xpack.security.enabled: true
2 | discovery.type: single-node
3 |
--------------------------------------------------------------------------------
/elasticsearch/jvm.options:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laffra/Ikke/5c0e0a4103e66de53469233c7c4a07995ad7d3d8/elasticsearch/jvm.options
--------------------------------------------------------------------------------
/extension/background.js:
--------------------------------------------------------------------------------
1 | var email = "???";
2 |
3 | const settings = {
4 | "debug-browser-extension": "",
5 | "show-ikke-dot": "",
6 | }
7 |
8 | chrome.identity.getProfileUserInfo(function(info) {
9 | email = info.email;
10 | });
11 |
12 | chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
13 | if (request.kind == "ikke-email") {
14 | sendResponse({email: email});
15 | }
16 | });
17 |
18 | function call(url, handler) {
19 | var xhr = new XMLHttpRequest();
20 | xhr.open("GET", url, true);
21 | xhr.addEventListener("load", function() {
22 | handler(this.responseText);
23 | });
24 | xhr.send();
25 | }
26 |
27 | chrome.runtime.onMessage.addListener(
28 | function(request, sender, sendResponse) {
29 | if (!sender.tab.active) return;
30 | switch (request.type) {
31 | case 'get-related-items':
32 | call('http://localhost:1964/get_related_items' +
33 | '?url=' + encodeURIComponent(request.url) +
34 | '&title=' + encodeURIComponent(request.title) +
35 | '&essence=' + encodeURIComponent(request.essence) +
36 | '&email=' + encodeURIComponent(email) +
37 | '&image=' + encodeURIComponent(request.image) +
38 | '&selection=' + encodeURIComponent(request.selection) +
39 | '&favicon=' + encodeURIComponent(request.favicon) +
40 | '&keywords=' + encodeURIComponent(request.keywords || ''), function(response) {
41 | sendResponse(JSON.parse(response));
42 | });
43 | break;
44 | }
45 | return true;
46 | }
47 | );
48 |
49 | function syncSetting(key) {
50 | call('http://localhost:1964/settings_get?key=' + key, function(response) {
51 | if (settings[key] != response) {
52 | settings[key] = response;
53 | sendMessage({ type: key, value: settings[key] });
54 | }
55 | }, true);
56 | }
57 |
58 | setInterval(function() {
59 | for (key in settings) {
60 | syncSetting(key);
61 | }
62 | }, 1000);
63 |
64 | function sendMessage(data) {
65 | chrome.tabs.query({}, function(tabs) {
66 | for (tab of tabs) {
67 | console.log("send", tab.id, data.kind);
68 | chrome.tabs.sendMessage(tab.id, data, function(response) {
69 | console.log(tab.id, response);
70 | });
71 | }
72 | });
73 | }
74 |
75 | function notifyTabs(activeInfo) {
76 | setTimeout(function() {
77 | for (key in settings) {
78 | sendMessage({ type: key, value: settings[key] });
79 | }
80 | sendMessage({ type: "tab-changed" });
81 | }, 100);
82 | }
83 |
84 | chrome.tabs.onUpdated.addListener(function(tabId) { notifyTabs({ tabId })});
85 | chrome.tabs.onActivated.addListener(notifyTabs);
86 |
87 | chrome.contextMenus.create({
88 | title: "Search Ikke for \"%s\"",
89 | contexts: ["selection"],
90 | onclick: function(info, tab) {
91 | console.log("search ikke", info);
92 | window.open('http://localhost:1964/?q=' + info.selectionText);
93 | },
94 | });
--------------------------------------------------------------------------------
/extension/ikke-1.4.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laffra/Ikke/5c0e0a4103e66de53469233c7c4a07995ad7d3d8/extension/ikke-1.4.zip
--------------------------------------------------------------------------------
/extension/ikke.css:
--------------------------------------------------------------------------------
1 | .ikke_sidebar {
2 | }
--------------------------------------------------------------------------------
/extension/jquery-addons.js:
--------------------------------------------------------------------------------
1 |
2 | $.fn.isInViewport = function(){
3 | var win = $(window);
4 | var viewport = {
5 | top : win.scrollTop(),
6 | left : win.scrollLeft()
7 | };
8 | viewport.right = viewport.left + win.width();
9 | viewport.bottom = viewport.top + win.height();
10 | var bounds = this.offset();
11 | bounds.right = bounds.left + this.outerWidth();
12 | bounds.bottom = bounds.top + this.outerHeight();
13 | return (!(viewport.right < bounds.left || viewport.left > bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom));
14 | };
15 |
--------------------------------------------------------------------------------
/extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Chris Laffra - laffra@gmail.com",
3 | "background": {
4 | "scripts": [ "background.js" ]
5 | },
6 | "content_scripts": [ {
7 | "all_frames": true,
8 | "js": [ "jquery.js", "jquery-addons.js", "jquery-ui.min.js", "settings.js", "contentscript.js" ],
9 | "css": [ "jquery-ui.min.css", "ikke.css" ],
10 | "matches": [ "http://*/*", "https://*/*" ]
11 | } ],
12 | "description": "Ikke",
13 | "manifest_version": 2,
14 | "name": "Ikke",
15 | "icons": {
16 | "16": "octopus.jpg",
17 | "48": "octopus.jpg",
18 | "128": "octopus.jpg"
19 | },
20 | "page_action": {
21 | "default_icon": {
22 | "38": "octopus.jpg"
23 | },
24 | "default_title": "Ikke"
25 | },
26 | "permissions": [ "tabs", "http://*/", "https://*/", "contextMenus", "identity", "identity.email" ],
27 | "short_name": "Ikke",
28 | "update_url": "https://clients2.google.com/service/update2/crx",
29 | "version": "1.8"
30 | }
31 |
--------------------------------------------------------------------------------
/extension/octopus.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laffra/Ikke/5c0e0a4103e66de53469233c7c4a07995ad7d3d8/extension/octopus.jpg
--------------------------------------------------------------------------------
/extension/settings.js:
--------------------------------------------------------------------------------
1 | if (document.location.href.startsWith('http://localhost:')) {
2 | $('#ikke-extension').css('display', 'none');
3 | $('#ikke-gmail-needed').css('display', 'block');
4 | $('#ikke-settings').css('display', 'block');
5 | }
6 |
--------------------------------------------------------------------------------
/facebooksdk/version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 Mobolic
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | __version__ = "3.0.0-alpha"
18 |
--------------------------------------------------------------------------------
/graph.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from collections import defaultdict
3 | import logging
4 | from random import random
5 | from re import I
6 | from urllib.parse import unquote
7 | import json
8 | import time
9 | from importers import browser
10 | from importers import contact
11 | from importers import gmail
12 | import classify
13 | from preferences import ChromePreferences
14 | import os
15 | import utils
16 | from threadpool import ThreadPool
17 | from storage import Storage
18 |
19 | days = {
20 | 'day': 1,
21 | 'week': 7,
22 | 'month': 31,
23 | 'month3': 92,
24 | 'month6': 182,
25 | 'year': 365,
26 | 'forever': 3650,
27 | }
28 | MY_EMAIL_ADDRESS = ChromePreferences().get_email()
29 | LINE_COLORS = [ '#f4c950', '#ee4e5a', '#489ac9', '#41ba7d', '#fb7c54',] * 2
30 |
31 | ALL_ITEM_KINDS = [ 'all', 'contact', 'gmail', 'calendar', 'git', 'hangouts', 'browser', 'file', ]
32 | MY_ITEM_KINDS = [ 'contact', 'gmail', 'calendar', 'git', 'hangouts', 'browser', 'file' ]
33 |
34 | MAX_LABEL_LENGTH = 42
35 | ADD_WORDS_MINIMUM_COUNT = 100
36 | REDUCE_GRAPH_SIZE = False
37 |
38 | IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'tiff', 'png', 'raw']
39 |
40 | logging.basicConfig(level=logging.INFO)
41 | logger = logging.getLogger(__name__)
42 |
43 | class Graph:
44 | def __init__(self, email, query, duration_string):
45 | logger.info('GRAPH: init %s %s' % (repr(query), duration_string))
46 | self.email = email
47 | self.query = query
48 | self.duration_string = duration_string
49 | self.search_count = {}
50 | self.search_results = defaultdict(list)
51 | self.search_duration = defaultdict(list)
52 | self.time_nodes = set()
53 | self.my_pool = ThreadPool(1, [
54 | (self.search, days[duration_string])
55 | ])
56 |
57 | def add_result(self, kind, count, items, duration):
58 | self.search_results[kind] = set(filter(None, self.search_results[kind] + list(items)))
59 | self.search_count[kind] = count
60 | self.search_duration[kind].append(duration)
61 | logger.debug('found %d of %s items' % (len(items), kind))
62 |
63 | def search(self, days):
64 | start_time = time.time()
65 | all_items = list(filter(None, Storage.search(unquote(self.query), days)))
66 | duration = time.time() - start_time
67 |
68 | self.add_result('all', len(all_items), all_items, duration)
69 | for kind in MY_ITEM_KINDS:
70 | items = [item for item in all_items if item.kind in ('label', kind)]
71 | self.add_result(kind, len(items), items, duration)
72 |
73 | def get_graph(self, kind, keep_duplicates):
74 | self.my_pool.wait_completion()
75 | all_found_items = self.search_results['all' if kind in ['contact', 'file'] else kind]
76 | found_items = [item for item in all_found_items if item.kind != 'contact' or item.label != self.email]
77 | add_words = True
78 | edges, items = classify.get_edges(self.query, found_items, add_words, keep_duplicates)
79 | removed_item_count = 0
80 | for item in found_items:
81 | if not item in items:
82 | removed_item_count += 1
83 | if kind in ['contact', 'file']:
84 | items = [item for item in items if item.kind == kind]
85 | if REDUCE_GRAPH_SIZE:
86 | items = self.remove_lonely_images(items)
87 | items = self.remove_lonely_labels(items)
88 | # items = self.remove_labels(items)
89 |
90 | for item in items:
91 | if len(item.label) > MAX_LABEL_LENGTH:
92 | cutoff = round(MAX_LABEL_LENGTH / 2)
93 | head = item.label[:cutoff]
94 | tail = item.label[-cutoff:]
95 | item.label = "%s...%s" % (head, tail)
96 | item.date = str(datetime.datetime.fromtimestamp(float(item.timestamp))) if item.timestamp else ""
97 | item.x = random() * 500
98 | item.y = random() * 500
99 |
100 | nodes_index = dict((item.uid, n) for n, item in enumerate(items))
101 | label_index = dict((item.label, n) for n, item in enumerate(items))
102 | nodes = [vars(item) for item in items]
103 | def get_color(item):
104 | return LINE_COLORS[label_index.get(item.label, 0) % len(LINE_COLORS)]
105 | links = [
106 | {
107 | 'source': nodes_index[item1.uid],
108 | 'target': nodes_index[item2.uid],
109 | 'color': get_color(item2),
110 | 'stroke': 1,
111 | }
112 | for item1, item2 in edges
113 | if item1 and item1.uid in nodes_index and item2 and item2.uid in nodes_index
114 | ]
115 |
116 | logger.debug("Found %d nodes" % len(nodes))
117 | for node in nodes:
118 | logger.debug(" %s" % node["kind"])
119 | logger.debug("Found %d links" % len(links))
120 |
121 | stats = {
122 | 'found': len(found_items),
123 | 'removed': removed_item_count,
124 | 'memory': utils.get_memory()
125 | }
126 | stats.update(Storage.stats)
127 | logger.info("Graph stats: %s" % json.dumps(stats))
128 | if kind == 'all':
129 | browser.cleanup()
130 | gmail.cleanup()
131 | contact.cleanup()
132 | Storage.stats.clear()
133 |
134 | return {
135 | 'graph': [],
136 | 'links': links,
137 | 'nodes': nodes,
138 | 'directed': False,
139 | 'stats': stats
140 | }
141 |
142 | def remove_labels(self, items):
143 | return [item for item in items if item.kind != 'label']
144 |
145 |
146 | def remove_lonely_images(self, items):
147 | return [item for item in items if not self.is_lonely_image(item)]
148 |
149 |
150 | def remove_lonely_labels(self, items):
151 | return [item for item in items if not self.is_lonely_label(item)]
152 |
153 |
154 | def is_lonely_image(self, item):
155 | if item.kind != "file":
156 | return False
157 | _, extension = os.path.splitext(item.path)
158 | return extension[1:].lower() in IMAGE_EXTENSIONS
159 |
160 |
161 | def is_lonely_label(self, item):
162 | return item.kind == "label" and item.edges == 0
163 |
--------------------------------------------------------------------------------
/hosts.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 |
5 | def get_etc_host_path():
6 | if os.name == 'nt':
7 | return os.path.join(os.path.sep, 'etc', 'hosts')
8 | else:
9 | return os.path.join(os.path.sep, 'private', 'etc', 'hosts')
10 |
11 |
12 | def setup(remote_url, local_url):
13 | with open(get_etc_host_path()) as fin:
14 | contents = fin.read()
15 | rule = '%s %s' % (local_url, remote_url)
16 | if rule not in contents:
17 | print('Adding rule to map %s to %s' % (remote_url, local_url))
18 | with open(get_etc_host_path(), 'a') as fout:
19 | fout.write('\n')
20 | fout.write(rule)
21 | fout.write('\n')
22 | clear_DNS()
23 | else:
24 | print('Rule to map %s to %s already present' % (remote_url, local_url))
25 |
26 |
27 | def clear_DNS():
28 | if os.name == 'nt':
29 | pass
30 | else:
31 | print('killall -HUP mDNSResponder')
32 | os.system('sudo dscacheutil -flushcache')
33 | os.system('sudo killall -HUP mDNSResponder')
34 | # send user to chrome://net-internals/#dns and clear the cache?
35 |
36 |
37 | def setup_as_administrator():
38 | if os.name == 'nt':
39 | pass
40 | else:
41 | script = os.path.join(os.getcwd(), "hosts.py")
42 | print("")
43 | print("To set up IKKE correctly, we need to add an extra DNS entry to your /private/etc/hosts file.")
44 | print("")
45 | print("This is the script being executed: %s" % script)
46 | print("")
47 | print("Please provide your adminstrator password.")
48 | print("")
49 | command = 'sudo %s %s' % (sys.executable, script)
50 | os.system('osascript -e \'tell application "Terminal" to do script "%s"\'' % command)
51 | print("")
52 | print("This script is launched in another Terminal (use Cmd+Tab to find it).")
53 |
54 |
55 | if __name__ == '__main__':
56 | print('Setting up etc/hosts rule for Ikke')
57 | try:
58 | setup('ikke', '127.0.0.1:1964')
59 | except PermissionError as e:
60 | print("Switching to sudo")
61 | setup_as_administrator()
62 |
--------------------------------------------------------------------------------
/html/3rd/jquery-ui-1.12.1/AUTHORS.txt:
--------------------------------------------------------------------------------
1 | Authors ordered by first contribution
2 | A list of current team members is available at http://jqueryui.com/about
3 |
4 | Paul Bakaus
5 | Richard Worth
6 | Yehuda Katz
7 | Sean Catchpole
8 | John Resig
9 | Tane Piper
10 | Dmitri Gaskin
11 | Klaus Hartl
12 | Stefan Petre
13 | Gilles van den Hoven
14 | Micheil Bryan Smith
15 | Jörn Zaefferer
16 | Marc Grabanski
17 | Keith Wood
18 | Brandon Aaron
19 | Scott González
20 | Eduardo Lundgren
21 | Aaron Eisenberger
22 | Joan Piedra
23 | Bruno Basto
24 | Remy Sharp
25 | Bohdan Ganicky
26 | David Bolter
27 | Chi Cheng
28 | Ca-Phun Ung
29 | Ariel Flesler
30 | Maggie Wachs
31 | Scott Jehl
32 | Todd Parker
33 | Andrew Powell
34 | Brant Burnett
35 | Douglas Neiner
36 | Paul Irish
37 | Ralph Whitbeck
38 | Thibault Duplessis
39 | Dominique Vincent
40 | Jack Hsu
41 | Adam Sontag
42 | Carl Fürstenberg
43 | Kevin Dalman
44 | Alberto Fernández Capel
45 | Jacek Jędrzejewski (http://jacek.jedrzejewski.name)
46 | Ting Kuei
47 | Samuel Cormier-Iijima
48 | Jon Palmer
49 | Ben Hollis
50 | Justin MacCarthy
51 | Eyal Kobrigo
52 | Tiago Freire
53 | Diego Tres
54 | Holger Rüprich
55 | Ziling Zhao
56 | Mike Alsup
57 | Robson Braga Araujo
58 | Pierre-Henri Ausseil
59 | Christopher McCulloh
60 | Andrew Newcomb
61 | Lim Chee Aun
62 | Jorge Barreiro
63 | Daniel Steigerwald
64 | John Firebaugh
65 | John Enters
66 | Andrey Kapitcyn
67 | Dmitry Petrov
68 | Eric Hynds
69 | Chairat Sunthornwiphat
70 | Josh Varner
71 | Stéphane Raimbault
72 | Jay Merrifield
73 | J. Ryan Stinnett
74 | Peter Heiberg
75 | Alex Dovenmuehle
76 | Jamie Gegerson
77 | Raymond Schwartz
78 | Phillip Barnes
79 | Kyle Wilkinson
80 | Khaled AlHourani
81 | Marian Rudzynski
82 | Jean-Francois Remy
83 | Doug Blood
84 | Filippo Cavallarin
85 | Heiko Henning
86 | Aliaksandr Rahalevich
87 | Mario Visic
88 | Xavi Ramirez
89 | Max Schnur
90 | Saji Nediyanchath
91 | Corey Frang
92 | Aaron Peterson
93 | Ivan Peters
94 | Mohamed Cherif Bouchelaghem
95 | Marcos Sousa
96 | Michael DellaNoce
97 | George Marshall
98 | Tobias Brunner
99 | Martin Solli
100 | David Petersen
101 | Dan Heberden
102 | William Kevin Manire
103 | Gilmore Davidson
104 | Michael Wu
105 | Adam Parod
106 | Guillaume Gautreau
107 | Marcel Toele
108 | Dan Streetman
109 | Matt Hoskins
110 | Giovanni Giacobbi
111 | Kyle Florence
112 | Pavol Hluchý
113 | Hans Hillen
114 | Mark Johnson
115 | Trey Hunner
116 | Shane Whittet
117 | Edward A Faulkner
118 | Adam Baratz
119 | Kato Kazuyoshi
120 | Eike Send
121 | Kris Borchers
122 | Eddie Monge
123 | Israel Tsadok
124 | Carson McDonald
125 | Jason Davies
126 | Garrison Locke
127 | David Murdoch
128 | Benjamin Scott Boyle
129 | Jesse Baird
130 | Jonathan Vingiano
131 | Dylan Just
132 | Hiroshi Tomita
133 | Glenn Goodrich
134 | Tarafder Ashek-E-Elahi
135 | Ryan Neufeld
136 | Marc Neuwirth
137 | Philip Graham
138 | Benjamin Sterling
139 | Wesley Walser
140 | Kouhei Sutou
141 | Karl Kirch
142 | Chris Kelly
143 | Jason Oster
144 | Felix Nagel
145 | Alexander Polomoshnov
146 | David Leal
147 | Igor Milla
148 | Dave Methvin
149 | Florian Gutmann
150 | Marwan Al Jubeh
151 | Milan Broum
152 | Sebastian Sauer
153 | Gaëtan Muller
154 | Michel Weimerskirch
155 | William Griffiths
156 | Stojce Slavkovski
157 | David Soms
158 | David De Sloovere
159 | Michael P. Jung
160 | Shannon Pekary
161 | Dan Wellman
162 | Matthew Edward Hutton
163 | James Khoury
164 | Rob Loach
165 | Alberto Monteiro
166 | Alex Rhea
167 | Krzysztof Rosiński
168 | Ryan Olton
169 | Genie <386@mail.com>
170 | Rick Waldron
171 | Ian Simpson
172 | Lev Kitsis
173 | TJ VanToll
174 | Justin Domnitz
175 | Douglas Cerna
176 | Bert ter Heide
177 | Jasvir Nagra
178 | Yuriy Khabarov <13real008@gmail.com>
179 | Harri Kilpiö
180 | Lado Lomidze
181 | Amir E. Aharoni
182 | Simon Sattes
183 | Jo Liss
184 | Guntupalli Karunakar
185 | Shahyar Ghobadpour
186 | Lukasz Lipinski
187 | Timo Tijhof
188 | Jason Moon
189 | Martin Frost
190 | Eneko Illarramendi
191 | EungJun Yi
192 | Courtland Allen
193 | Viktar Varvanovich
194 | Danny Trunk
195 | Pavel Stetina
196 | Michael Stay
197 | Steven Roussey
198 | Michael Hollis
199 | Lee Rowlands
200 | Timmy Willison
201 | Karl Swedberg
202 | Baoju Yuan
203 | Maciej Mroziński
204 | Luis Dalmolin
205 | Mark Aaron Shirley
206 | Martin Hoch
207 | Jiayi Yang
208 | Philipp Benjamin Köppchen
209 | Sindre Sorhus
210 | Bernhard Sirlinger
211 | Jared A. Scheel
212 | Rafael Xavier de Souza
213 | John Chen
214 | Robert Beuligmann
215 | Dale Kocian
216 | Mike Sherov
217 | Andrew Couch
218 | Marc-Andre Lafortune
219 | Nate Eagle
220 | David Souther
221 | Mathias Stenbom
222 | Sergey Kartashov
223 | Avinash R
224 | Ethan Romba
225 | Cory Gackenheimer
226 | Juan Pablo Kaniefsky
227 | Roman Salnikov
228 | Anika Henke
229 | Samuel Bovée
230 | Fabrício Matté
231 | Viktor Kojouharov
232 | Pawel Maruszczyk (http://hrabstwo.net)
233 | Pavel Selitskas
234 | Bjørn Johansen
235 | Matthieu Penant
236 | Dominic Barnes
237 | David Sullivan
238 | Thomas Jaggi
239 | Vahid Sohrabloo
240 | Travis Carden
241 | Bruno M. Custódio
242 | Nathanael Silverman
243 | Christian Wenz
244 | Steve Urmston
245 | Zaven Muradyan
246 | Woody Gilk
247 | Zbigniew Motyka
248 | Suhail Alkowaileet
249 | Toshi MARUYAMA
250 | David Hansen
251 | Brian Grinstead
252 | Christian Klammer
253 | Steven Luscher
254 | Gan Eng Chin
255 | Gabriel Schulhof
256 | Alexander Schmitz
257 | Vilhjálmur Skúlason
258 | Siebrand Mazeland
259 | Mohsen Ekhtiari
260 | Pere Orga
261 | Jasper de Groot