├── LICENSE ├── MANIFEST ├── README.md ├── dayonelib └── __init__.py ├── dist └── dayonelib-0.1.tar.gz ├── examples ├── pagico_dayone │ ├── README.md │ └── log_interactions.py └── todoist_dayone │ ├── README.md │ ├── todoist_dayone.py │ └── todoistdayone.config ├── setup.cfg └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Phil Jackson 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 deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | dayonelib/__init__.py 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DayOneLib 2 | 3 | A [Python](http://python.org) library for creating [DayOne](http://dayoneapp.com/) entries. 4 | 5 | ## Installation 6 | `` 7 | pip install dayonelib 8 | `` 9 | ### Dependencies 10 | * tzlocal 11 | * pyobjc-framework-CoreLocation 12 | * plistlib 13 | * uuid 14 | * arrow 15 | 16 | ## About 17 | There are numerous tools to interact with DayOne. I have used [jrnl](https://maebert.github.io/jrnl/) and the DayOne CLI extensively in the past. However, they were both missing a few features. I also found that I had to write a lot of redundant code to use these tools. 18 | 19 | This lib was created to offload some of the common chores of creating entries for DayOne, to provide a fuller set of features, and to make scripts interacting with DayOne cleaner. 20 | 21 | 22 | Each DayOneEntry is only required to have the `text` property populated to create the journal entry. 23 | 24 | dayonelib features: 25 | * Location 26 | * Will use Location Services to find current location 27 | * Tags 28 | * Entry Text 29 | * Date 30 | * Starring 31 | * UUID 32 | 33 | Most of these will be automatically populated by DayOneLib but can be overloaded with custom values. The location serivce will require you to allow python access to location in the MacOS Privacy settings. 34 | 35 | ## Hello World 36 | ```python 37 | import dayonelib 38 | journal_location = '' 39 | 40 | dayone = dayonelib.DayOne(journal_location) 41 | 42 | entry = dayonelib.DayOneEntry() 43 | entry.text = "I tried out dayonelib today!" 44 | dayone.save(entry) 45 | ``` 46 | 47 | Additional examples can be found in `examples/` 48 | *Heads up they are pretty dirty* 49 | 50 | ## Many Thanks 51 | * [Kevin Landreth](https://github.com/crackerjackmack) 52 | * [Kevin McDonald](https://github.com/sudorandom) 53 | -------------------------------------------------------------------------------- /dayonelib/__init__.py: -------------------------------------------------------------------------------- 1 | import CoreLocation 2 | import arrow 3 | import plistlib 4 | import time 5 | import tzlocal 6 | import uuid 7 | import os.path 8 | import datetime 9 | 10 | 11 | class DayOneEntry(object): 12 | def __init__(self, text=None, starred=False, tags=None): 13 | self.tags = tags or [] 14 | self.starred = starred 15 | self.text = text 16 | # We default to now time. Override it if needed 17 | self.time = arrow.utcnow().format('YYYY-MM-DDTHH:mm:ssz') 18 | self.lat = None 19 | self.lon = None 20 | self.starred = False 21 | self.tags = [] 22 | self.text = None 23 | self._uuid = None 24 | self._tz = None 25 | self.location = {} 26 | 27 | 28 | @property 29 | def tz(self): 30 | """Return the timezone. If none is set use system timezone""" 31 | if not self._tz: 32 | self._tz = tzlocal.get_localzone().zone 33 | return self._tz 34 | 35 | 36 | @tz.setter 37 | def tz(self, timezone): 38 | self._tz = timezone 39 | 40 | 41 | def add_tag(self, _tags): 42 | """Add tag(s) to a DayOneEntry""" 43 | if isinstance(_tags, list): 44 | for t in _tags: 45 | self.tags.append(t) 46 | else: 47 | self.tags.append(_tags) 48 | 49 | @property 50 | def uuid(self): 51 | """Return _uuid""" 52 | return self._uuid 53 | 54 | 55 | @uuid.setter 56 | def uuid(self, value): 57 | """ Set _uuid to specified value""" 58 | self._uuid = value 59 | 60 | 61 | @property 62 | def time(self): 63 | """Return the DayOneEntry's time""" 64 | return self._time 65 | 66 | 67 | @time.setter 68 | def time(self, t): 69 | """Convert any timestamp into a datetime and save as _time""" 70 | _time = arrow.get(t).format('YYYY-MM-DDTHH:mm:ss') 71 | self._time = datetime.datetime.strptime(_time, '%Y-%m-%dT%H:%M:%S') 72 | 73 | 74 | def as_dict(self): 75 | """Return a dict that represents the DayOneEntry""" 76 | entry_dict = {} 77 | entry_dict['UUID'] = self.uuid 78 | entry_dict['Creation Date'] = self.time 79 | entry_dict['Time Zone'] = self.tz 80 | if self.tags: 81 | entry_dict['Tags'] = self.tags 82 | entry_dict['Entry Text'] = self.text 83 | entry_dict['Starred'] = self.starred 84 | entry_dict['Location'] = self.location 85 | return entry_dict 86 | 87 | 88 | class DayOne(object): 89 | def __init__(self, dayone_journal_path=None): 90 | self.dayone_journal_path = dayone_journal_path 91 | self._location_manager = CoreLocation.CLLocationManager.alloc().init() 92 | self._location_manager.delegate() 93 | 94 | def get_location(self): 95 | self._location_manager.startUpdatingLocation() 96 | loc = None 97 | while loc is None: 98 | loc = self._location_manager.location() 99 | time.sleep(0.25) 100 | coord = loc.coordinate() 101 | self._location_manager.stopUpdatingLocation() 102 | lat, lon = coord.latitude, coord.longitude 103 | 104 | loc = { 105 | "Latitude": lat, 106 | "Longitude": lon 107 | } 108 | 109 | return loc 110 | 111 | 112 | def save(self, entry, with_location=True, debug=False): 113 | """Saves a DayOneEntry as a plist""" 114 | entry_dict = {} 115 | if isinstance(entry, DayOneEntry): 116 | # Get a dict of the DayOneEntry 117 | entry_dict = entry.as_dict() 118 | else: 119 | entry_dict = entry 120 | 121 | # Set the UUID 122 | entry_dict['UUID'] = uuid.uuid4().get_hex() 123 | if with_location and not entry_dict['Location']: 124 | entry_dict['Location'] = self.get_location() 125 | 126 | 127 | # Do we have everything needed? 128 | if not all ((entry_dict['UUID'], entry_dict['Time Zone'], 129 | entry_dict['Entry Text'])): 130 | print "You must provide: Time zone, UUID, Creation Date, Entry Text" 131 | return False 132 | 133 | if debug is False: 134 | file_path = self._file_path(entry_dict['UUID']) 135 | plistlib.writePlist(entry_dict, file_path) 136 | else: 137 | plist = plistlib.writePlistToString(entry_dict) 138 | print plist 139 | 140 | return True 141 | 142 | 143 | def _file_path(self, uid): 144 | """Create and return full file path for DayOne entry""" 145 | file_name = '%s.doentry' % (uid) 146 | return os.path.join(self.dayone_journal_path, file_name) 147 | 148 | -------------------------------------------------------------------------------- /dist/dayonelib-0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underscorephil/dayonelib/4df134f601abcb033ec04cf7596f25ee25d44661/dist/dayonelib-0.1.tar.gz -------------------------------------------------------------------------------- /examples/pagico_dayone/README.md: -------------------------------------------------------------------------------- 1 | # Log Pagico Interactions 2 | Create a [DayOne](https://dayoneapp.com) entry for each note on a contacts page. 3 | -------------------------------------------------------------------------------- /examples/pagico_dayone/log_interactions.py: -------------------------------------------------------------------------------- 1 | import sqlite3 as lite 2 | from phpserialize import * 3 | import time, os.path, re, uuid, plistlib 4 | from datetime import datetime 5 | from pprint import pprint as pp 6 | import html2text, json 7 | import CoreLocation 8 | import sys 9 | import arrow 10 | import dayonelib 11 | 12 | journal_location = '' 13 | 14 | dayone = dayonelib.DayOne(journal_location) 15 | 16 | 17 | con = lite.connect('/database/main.db') 18 | 19 | config_file_path = os.path.join(os.path.expanduser("~"), '.pagicodayone') 20 | with open(config_file_path, 'r') as cfg_file: 21 | config = json.load(cfg_file) 22 | 23 | cur_time = int(time.time()) 24 | 25 | 26 | if not config['lastrun']: 27 | print 'no cfg' 28 | print config['lastrun'] 29 | config['lastrun'] = cur_time 30 | 31 | 32 | with con: 33 | 34 | con.row_factory = lite.Row 35 | cur = con.cursor() 36 | note_query = 'SELECT * FROM mach WHERE Modified > %s AND Type="Text" AND Deleted IS NULL' % (config['lastrun']) 37 | cur.execute(note_query) 38 | config['lastrun'] = cur_time 39 | 40 | with open(config_file_path, 'w') as cfg_file: 41 | json.dump(config, cfg_file) 42 | 43 | rows = cur.fetchall() 44 | 45 | for row in rows: 46 | entry_date = row['Modified'] 47 | 48 | entry = dayonelib.DayOneEntry() 49 | entry.time = entry_date 50 | entry.tags = ['pagico', 'interaction'] 51 | 52 | # unseralize the body of the note 53 | row_content = loads(row['Content']) 54 | 55 | # entry body text 56 | entry_text = "%s" % (row_content['Body']) 57 | entry_text = html2text.html2text(entry_text) 58 | 59 | 60 | # entry title 61 | entry_title = row_content['Title'] 62 | 63 | # All notes on a contact will have a parent. Skip anything without a parent 64 | if row["ParentID"] is not None: 65 | # Get contact info 66 | parent_query = 'SELECT * FROM mach WHERE UID="%s"' % (row['ParentID']) 67 | cur.execute(parent_query) 68 | parent = cur.fetchone() 69 | parent_content = loads(parent['content']) 70 | 71 | # Make sure the parent is a contact(type Profile) 72 | if parent['Type'] == 'Profile': 73 | # add contact tame as tag 74 | entry.add_tag(( "%s_%s" % (parent_content['firstName'], parent_content.get('lastName', '')))) 75 | 76 | entry.text = '# interaction: %s - %s\n\n%s' % ( 77 | entry_title, parent_content['Name'], entry_text) 78 | 79 | # @ tag syntax 80 | pat = re.compile(r"@(\w+)") 81 | inline_tags = pat.findall(entry_text) 82 | # pagico link syntax 83 | links = re.compile(r"\[(.*?)\]") 84 | inline_links = links.findall(entry_text) 85 | 86 | # add [links] in pagico as a tag in dayone 87 | for t in inline_links: 88 | entry.add_tag(t.replace(" ", "_")) 89 | # add @tags in pagico to dayone 90 | for t in inline_tags: 91 | entry.add_tag((t)) 92 | 93 | dayone.save(entry, True) 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /examples/todoist_dayone/README.md: -------------------------------------------------------------------------------- 1 | # todoist_dayone 2 | Log completed [Todoist](https://todoist.com/) tasks to [Day One](http://dayoneapp.com/) 3 | 4 | ## Usage 5 | When no date is present in the config file the current date will be used and 6 | stored in the config as `lastrun` 7 | 8 | 1. Copy the config file to your home directory as `.todoistdayone` 9 | 2. Edit the config with your API key and desired start date 10 | 3. Run the script 11 | 4. Create a cron to automatically run the script 12 | 13 | -------------------------------------------------------------------------------- /examples/todoist_dayone/todoist_dayone.py: -------------------------------------------------------------------------------- 1 | import requests, json, time, os.path 2 | from subprocess import call 3 | import dayonelib 4 | 5 | journal_location = '' 6 | 7 | dayone = dayonelib.DayOne(journal_location) 8 | 9 | config_file_path = os.path.join(os.path.expanduser("~"), '.todoistdayone') 10 | with open(config_file_path, 'r') as cfg_file: 11 | config = json.load(cfg_file) 12 | 13 | url = "https://todoist.com/API/v6/get_all_completed_items" 14 | 15 | cur_time = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) 16 | 17 | if not config['lastrun']: 18 | config['lastrun'] = cur_time 19 | 20 | post_data = {"token": config['token'], 21 | "annotate_notes": "true", "since": "2016-03-08T20:06:00"} 22 | 23 | r = requests.post(url, post_data) 24 | 25 | data = json.loads(r.text) 26 | 27 | config['lastrun'] = cur_time 28 | 29 | with open(config_file_path, 'w') as cfg_file: 30 | json.dump(config, cfg_file) 31 | 32 | for task in data['items']: 33 | entry = dayonelib.DayOneEntry() 34 | 35 | project_id = task['project_id'] 36 | api_time = time.strptime(task['completed_date'], 37 | "%a %d %b %Y %H:%M:%S +0000") 38 | task_time = time.localtime(time.mktime(api_time) - time.altzone) 39 | 40 | notes = "" 41 | if task['notes']: 42 | notes = "\n## Notes:\n" 43 | for note in task['notes']: 44 | notes += "\n### %s\n %s\n" % (note['posted'], note['content']) 45 | text = "%s @%s_project @done @todoist%s" % (task['content'], 46 | data['projects'][str(task['project_id'])]['name'].replace(' ', '_'), notes) 47 | 48 | entry.text = text 49 | entry.time = api_time 50 | entry.tz = "UTC" 51 | dayone.save(entry) 52 | -------------------------------------------------------------------------------- /examples/todoist_dayone/todoistdayone.config: -------------------------------------------------------------------------------- 1 | {"lastrun": "2015-10-04T15:03:04", "token": "arst1234"} 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='dayonelib', 6 | version='0.2', 7 | packages=['dayonelib'], 8 | description='Tool for DayOne entry automation', 9 | author='Phil Jackson', 10 | author_email='opensource@underscorephil.com', 11 | url='https://www.github.com/underscorephil/dayonelib/', 12 | download_url='https://github.com/underscorephil/dayonelib/tarball/0.2', 13 | license='MIT License', 14 | keywords=['dayone'] 15 | 16 | ) 17 | --------------------------------------------------------------------------------