timetree-exporter 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. timetree-exporter-0.1.0/.github/dependabot.yml +10 -0
  2. timetree-exporter-0.1.0/.github/workflows/pylint.yml +24 -0
  3. timetree-exporter-0.1.0/.github/workflows/python-publish.yml +39 -0
  4. timetree-exporter-0.1.0/.github/workflows/release-please.yml +19 -0
  5. timetree-exporter-0.1.0/.gitignore +7 -0
  6. timetree-exporter-0.1.0/.vscode/settings.json +4 -0
  7. timetree-exporter-0.1.0/CHANGELOG.md +32 -0
  8. timetree-exporter-0.1.0/LICENSE +21 -0
  9. timetree-exporter-0.1.0/PKG-INFO +110 -0
  10. timetree-exporter-0.1.0/README.md +92 -0
  11. timetree-exporter-0.1.0/assets/images/copy-response.png +0 -0
  12. timetree-exporter-0.1.0/assets/images/prepare-for-signin.png +0 -0
  13. timetree-exporter-0.1.0/pyproject.toml +36 -0
  14. timetree-exporter-0.1.0/requirements.txt +1 -0
  15. timetree-exporter-0.1.0/responses/README.md +49 -0
  16. timetree-exporter-0.1.0/setup.cfg +4 -0
  17. timetree-exporter-0.1.0/timetree_exporter/__init__.py +4 -0
  18. timetree-exporter-0.1.0/timetree_exporter/__main__.py +57 -0
  19. timetree-exporter-0.1.0/timetree_exporter/event.py +70 -0
  20. timetree-exporter-0.1.0/timetree_exporter/formatter.py +164 -0
  21. timetree-exporter-0.1.0/timetree_exporter/utils.py +28 -0
  22. timetree-exporter-0.1.0/timetree_exporter.egg-info/PKG-INFO +110 -0
  23. timetree-exporter-0.1.0/timetree_exporter.egg-info/SOURCES.txt +25 -0
  24. timetree-exporter-0.1.0/timetree_exporter.egg-info/dependency_links.txt +1 -0
  25. timetree-exporter-0.1.0/timetree_exporter.egg-info/entry_points.txt +2 -0
  26. timetree-exporter-0.1.0/timetree_exporter.egg-info/requires.txt +1 -0
  27. timetree-exporter-0.1.0/timetree_exporter.egg-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/" # Location of package manifests
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/" # Location of package manifests
9
+ schedule:
10
+ interval: "weekly"
@@ -0,0 +1,24 @@
1
+ name: Pylint
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.8", "3.9", "3.10", "3.11"]
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Set up Python ${{ matrix.python-version }}
14
+ uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+ - name: Install dependencies
18
+ run: |
19
+ python -m pip install --upgrade pip
20
+ pip install pylint
21
+ python -m pip install -r requirements.txt
22
+ - name: Analysing the code with pylint
23
+ run: |
24
+ pylint $(git ls-files '*.py')
@@ -0,0 +1,39 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ release:
13
+ types: [published]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ deploy:
20
+
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v3
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v3
27
+ with:
28
+ python-version: '3.x'
29
+ - name: Install dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ pip install build
33
+ - name: Build package
34
+ run: python -m build
35
+ - name: Publish package
36
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37
+ with:
38
+ user: __token__
39
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,19 @@
1
+ on:
2
+ push:
3
+ branches:
4
+ - main
5
+
6
+ permissions:
7
+ contents: write
8
+ pull-requests: write
9
+
10
+ name: release-please
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: google-github-actions/release-please-action@v4
17
+ with:
18
+ token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
19
+ release-type: python
@@ -0,0 +1,7 @@
1
+ responses/sync*
2
+ timetree.ics
3
+ .venv
4
+ .DS_Store
5
+ *.pyc
6
+ dist
7
+ *.egg-info
@@ -0,0 +1,4 @@
1
+ {
2
+ "editor.defaultFormatter": "ms-python.black-formatter",
3
+ "editor.formatOnSave": true
4
+ }
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2024-04-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * argparse ([#12](https://github.com/eoleedi/TimeTree-exporter/issues/12)) ([1eae588](https://github.com/eoleedi/TimeTree-exporter/commit/1eae588f96e462dc12f9c5998c88b5582c25e0d5))
9
+ * created & last-modify ([fab8d76](https://github.com/eoleedi/TimeTree-exporter/commit/fab8d76c380c175cc4b7e8cba6fbc740bafe31f6))
10
+ * first commit - basic title, time, and alert function ([bac0eca](https://github.com/eoleedi/TimeTree-exporter/commit/bac0ecab5f9d778f9e5113c988cbbcf024367600))
11
+ * geo (location lat & lon) ([3547100](https://github.com/eoleedi/TimeTree-exporter/commit/3547100430ab817aea98937e6e8ab4e3cc33fea3))
12
+ * note and url ([61adb88](https://github.com/eoleedi/TimeTree-exporter/commit/61adb887f35d1d456b610a6ac19bcf35b5b96438))
13
+ * recurrences ([5c37911](https://github.com/eoleedi/TimeTree-exporter/commit/5c37911b584ba022f2114340612ee5572d8ec265))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * full day event & timezone issue ([41467ab](https://github.com/eoleedi/TimeTree-exporter/commit/41467ab0942c8a5ded425bbe73ca44de62481d56))
19
+ * write to file after parsing all the files ([bc0fd7f](https://github.com/eoleedi/TimeTree-exporter/commit/bc0fd7f20c12410cf2e548b4c419f89a775a5845))
20
+
21
+
22
+ ### Documentation
23
+
24
+ * add detailed description & remove the API limitation ([edeea3a](https://github.com/eoleedi/TimeTree-exporter/commit/edeea3aacfa64acaf5479912350c219941845702))
25
+ * add detailed instructions and info ([70c1e89](https://github.com/eoleedi/TimeTree-exporter/commit/70c1e89ec8a6b7172919f02c37ca54964953f911))
26
+ * add newline ([cc5275a](https://github.com/eoleedi/TimeTree-exporter/commit/cc5275a33a01bf1c67db22ed01b5e7402fcf17c2))
27
+ * add recommendation and remove in-development warning ([776d5f2](https://github.com/eoleedi/TimeTree-exporter/commit/776d5f271b8127c724f8d3be03e54e1ab41e52b1))
28
+ * bump icalendar from 5.0.11 to 5.0.12 ([a934c7b](https://github.com/eoleedi/TimeTree-exporter/commit/a934c7bdc53b8206ef7e37af7af3a0585a5d0abc))
29
+ * finish all day ([e23021e](https://github.com/eoleedi/TimeTree-exporter/commit/e23021e24cc9f038bdc070eea530f331bb3e1fde))
30
+ * fix typo ([248b669](https://github.com/eoleedi/TimeTree-exporter/commit/248b669c7027f37035778385d38902ec569ddf70))
31
+ * update document for pip installation method ([83cfaea](https://github.com/eoleedi/TimeTree-exporter/commit/83cfaea4ec55ad38836e9cd7c11896343b1915f9))
32
+ * use absolute path for images ([0bf9e33](https://github.com/eoleedi/TimeTree-exporter/commit/0bf9e33da0e2afe8ae84b085e07357b47ade1080))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 蔡鳳駿
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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.1
2
+ Name: timetree-exporter
3
+ Version: 0.1.0
4
+ Summary: A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics)
5
+ Author-email: Fong-Chun Tsai <eoleedimin@gmail.com>
6
+ Project-URL: Homepage, https://github.com/eoleedi/TimeTree-Exporter
7
+ Project-URL: Repository, https://github.com/eoleedi/TimeTree-Exporter
8
+ Project-URL: Issues, https://github.com/eoleedi/TimeTree-Exporter/issues
9
+ Project-URL: Changelog, https://github.com/eoleedi/TimeTree-Exporter/CHANGELOG.md
10
+ Keywords: timetree,exporter,icalendar,ics
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: icalendar
18
+
19
+ # TimeTree Exporter
20
+ A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics) \
21
+ (The .ics file can then be imported into other calendar apps such as Google Calendar, Apple Calendar, Outlook Calendar, etc.)
22
+
23
+ # Usage
24
+
25
+ First, download the timetree data manually and put it in the responses folder(see below)
26
+
27
+ ## Method 1: Pip
28
+ 1. Install the package `pip3 install timetree-exporter`
29
+ 2. Run `timetree-exporter <path-to-responses-folder>`
30
+ 3. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
31
+
32
+ ## Method 2: Cloning the repository
33
+ 1. Clone the repository `git clone https://github.com/eoleedi/TimeTree-exporter.git`
34
+ 2. Change the directory to the project folder `cd TimeTree-exporter`
35
+ 3. Install the requirements `pip3 install -r requirements.txt`
36
+ 4. Run `python3 -m timetree_exporter responses`
37
+ 5. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
38
+
39
+ ## How to download timetree data
40
+ 1. Go to [https://timetreeapp.com/signin](https://timetreeapp.com/signin)
41
+ 2. Open the developer tools before logging in
42
+ > Can be opened by pressing F12
43
+ 3. Go to the network tab
44
+ > Remember to Press Ctrl + R to start recording
45
+ 4. Turn on preserve log
46
+ 5. Type in "sync" in the filter box
47
+ 6. Log in
48
+ 7. Click on the calendar you want to export
49
+ 8. You will see couple of requests with the name "sync"
50
+ > There seems to be a maximum of 300 events in single requests. \
51
+ > If you have more than 300 events, you will see multiple requests with the name "sync"
52
+ 9. Right click on the request and select "Copy Response"
53
+ 10. Paste it into a json file under the response folder under this project (etc. sync.json)
54
+ > The file name should end with .json
55
+ 11. Do the same thing for all the requests with steps 7~10
56
+
57
+ ![Prepare for signin: Step 1~5](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/prepare-for-signin.png)
58
+ *Prepare for signin: Step 1~5*
59
+ ![Copy response: Step 8~9](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/copy-response.png)
60
+ *Copy response: Step 8~9*
61
+
62
+ # Advanced Usage
63
+ You can also specify the output file name by running `python3 -m timetree_exporter -o <output_file_name>.ics` or `timetree-exporter -o <output_file_name>.ics`
64
+
65
+ # Recommendation
66
+ You are recommended to import the ics file into a separate calendar (take google calendar as an example) as if anything goes wrong, you can just delete the calendar and reimport it.
67
+
68
+ # Requirements
69
+ icalendar==5.0.12
70
+
71
+ # Limitations
72
+ Currently, TimeTree data can only be downloaded manually through a web browser.
73
+
74
+ # Roadmap of the properties mapping to iCal
75
+ - [ ] **ID**
76
+ - [ ] **Primary ID**
77
+ - [ ] **Calendar ID**
78
+ - [x] **UUID**
79
+ - [ ] **Category**
80
+ - [ ] **Type**
81
+ - [ ] **Author ID**
82
+ - [ ] **Author Type**
83
+ - [x] **Title**
84
+ - [x] **All Day**
85
+ - [x] **Start At**
86
+ - [x] **Start Timezone**
87
+ - [x] **End At**
88
+ - [x] **End Timezone**
89
+ - [ ] **Label ID**
90
+ - [x] **Location**
91
+ - [x] **Location Latitude**
92
+ - [x] **Location Longitude**
93
+ - [x] **URL**
94
+ - [x] **Note**
95
+ - [ ] **Lunar**
96
+ - [ ] **Attendees**
97
+ - [x] **Recurrences**
98
+ - [ ] **Recurring UUID**
99
+ - [x] **Alerts**
100
+ - [ ] **Parent ID**
101
+ - [ ] **Link Object ID**
102
+ - [ ] **Link Object ID String**
103
+ - [ ] **Row Order**
104
+ - [ ] **Attachment**
105
+ - [ ] **Like Count**
106
+ - [ ] **Files**
107
+ - [ ] **Deactivated At**
108
+ - [ ] **Pinned At**
109
+ - [x] **Updated At**
110
+ - [x] **Created At**
@@ -0,0 +1,92 @@
1
+ # TimeTree Exporter
2
+ A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics) \
3
+ (The .ics file can then be imported into other calendar apps such as Google Calendar, Apple Calendar, Outlook Calendar, etc.)
4
+
5
+ # Usage
6
+
7
+ First, download the timetree data manually and put it in the responses folder(see below)
8
+
9
+ ## Method 1: Pip
10
+ 1. Install the package `pip3 install timetree-exporter`
11
+ 2. Run `timetree-exporter <path-to-responses-folder>`
12
+ 3. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
13
+
14
+ ## Method 2: Cloning the repository
15
+ 1. Clone the repository `git clone https://github.com/eoleedi/TimeTree-exporter.git`
16
+ 2. Change the directory to the project folder `cd TimeTree-exporter`
17
+ 3. Install the requirements `pip3 install -r requirements.txt`
18
+ 4. Run `python3 -m timetree_exporter responses`
19
+ 5. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
20
+
21
+ ## How to download timetree data
22
+ 1. Go to [https://timetreeapp.com/signin](https://timetreeapp.com/signin)
23
+ 2. Open the developer tools before logging in
24
+ > Can be opened by pressing F12
25
+ 3. Go to the network tab
26
+ > Remember to Press Ctrl + R to start recording
27
+ 4. Turn on preserve log
28
+ 5. Type in "sync" in the filter box
29
+ 6. Log in
30
+ 7. Click on the calendar you want to export
31
+ 8. You will see couple of requests with the name "sync"
32
+ > There seems to be a maximum of 300 events in single requests. \
33
+ > If you have more than 300 events, you will see multiple requests with the name "sync"
34
+ 9. Right click on the request and select "Copy Response"
35
+ 10. Paste it into a json file under the response folder under this project (etc. sync.json)
36
+ > The file name should end with .json
37
+ 11. Do the same thing for all the requests with steps 7~10
38
+
39
+ ![Prepare for signin: Step 1~5](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/prepare-for-signin.png)
40
+ *Prepare for signin: Step 1~5*
41
+ ![Copy response: Step 8~9](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/copy-response.png)
42
+ *Copy response: Step 8~9*
43
+
44
+ # Advanced Usage
45
+ You can also specify the output file name by running `python3 -m timetree_exporter -o <output_file_name>.ics` or `timetree-exporter -o <output_file_name>.ics`
46
+
47
+ # Recommendation
48
+ You are recommended to import the ics file into a separate calendar (take google calendar as an example) as if anything goes wrong, you can just delete the calendar and reimport it.
49
+
50
+ # Requirements
51
+ icalendar==5.0.12
52
+
53
+ # Limitations
54
+ Currently, TimeTree data can only be downloaded manually through a web browser.
55
+
56
+ # Roadmap of the properties mapping to iCal
57
+ - [ ] **ID**
58
+ - [ ] **Primary ID**
59
+ - [ ] **Calendar ID**
60
+ - [x] **UUID**
61
+ - [ ] **Category**
62
+ - [ ] **Type**
63
+ - [ ] **Author ID**
64
+ - [ ] **Author Type**
65
+ - [x] **Title**
66
+ - [x] **All Day**
67
+ - [x] **Start At**
68
+ - [x] **Start Timezone**
69
+ - [x] **End At**
70
+ - [x] **End Timezone**
71
+ - [ ] **Label ID**
72
+ - [x] **Location**
73
+ - [x] **Location Latitude**
74
+ - [x] **Location Longitude**
75
+ - [x] **URL**
76
+ - [x] **Note**
77
+ - [ ] **Lunar**
78
+ - [ ] **Attendees**
79
+ - [x] **Recurrences**
80
+ - [ ] **Recurring UUID**
81
+ - [x] **Alerts**
82
+ - [ ] **Parent ID**
83
+ - [ ] **Link Object ID**
84
+ - [ ] **Link Object ID String**
85
+ - [ ] **Row Order**
86
+ - [ ] **Attachment**
87
+ - [ ] **Like Count**
88
+ - [ ] **Files**
89
+ - [ ] **Deactivated At**
90
+ - [ ] **Pinned At**
91
+ - [x] **Updated At**
92
+ - [x] **Created At**
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools", "setuptools-scm"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ include = ["timetree_exporter"]
7
+
8
+ [project]
9
+ name = "timetree-exporter"
10
+ dynamic = ["version"]
11
+ authors = [
12
+ { name="Fong-Chun Tsai", email="eoleedimin@gmail.com" },
13
+ ]
14
+ description = "A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics)"
15
+ keywords = ["timetree", "exporter", "icalendar", "ics"]
16
+ readme = "README.md"
17
+ requires-python = ">=3.8"
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+ dependencies = [
24
+ "icalendar",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/eoleedi/TimeTree-Exporter"
29
+ Repository = "https://github.com/eoleedi/TimeTree-Exporter"
30
+ Issues = "https://github.com/eoleedi/TimeTree-Exporter/issues"
31
+ Changelog = "https://github.com/eoleedi/TimeTree-Exporter/CHANGELOG.md"
32
+
33
+ [project.scripts]
34
+ timetree-exporter = "timetree_exporter.__main__:main"
35
+
36
+ [tool.setuptools_scm]
@@ -0,0 +1 @@
1
+ icalendar==5.0.12
@@ -0,0 +1,49 @@
1
+ # This is the folder to put the json responses from timetree
2
+ ## Here is an exmaple format of the response
3
+ ```json
4
+ {
5
+ "since": null,
6
+ "chunk": null,
7
+ "events": [
8
+ {
9
+ "id": null,
10
+ "primary_id": null,
11
+ "calendar_id": null,
12
+ "uuid": null,
13
+ "category": null,
14
+ "type": null,
15
+ "author_id": null,
16
+ "author_type": null,
17
+ "title": null,
18
+ "all_day": null,
19
+ "start_at": null,
20
+ "start_timezone": null,
21
+ "end_at": null,
22
+ "end_timezone": null,
23
+ "label_id": null,
24
+ "location": null,
25
+ "location_lat": null,
26
+ "location_lon": null,
27
+ "url": null,
28
+ "note": null,
29
+ "lunar": null,
30
+ "attendees": [],
31
+ "recurrences": [],
32
+ "recurring_uuid": null,
33
+ "alerts": [],
34
+ "parent_id": null,
35
+ "link_object_id": null,
36
+ "link_object_id_string": null,
37
+ "row_order": null,
38
+ "attachment": {},
39
+ "like_count": null,
40
+ "files": [],
41
+ "deactivated_at": null,
42
+ "pinned_at": null,
43
+ "updated_at": null,
44
+ "created_at": null
45
+ },
46
+ ... more events
47
+ ]
48
+ }
49
+ ```
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ """Init file for timetree_exporter package."""
2
+
3
+ from timetree_exporter.event import TimeTreeEvent
4
+ from timetree_exporter.formatter import ICalEventFormatter
@@ -0,0 +1,57 @@
1
+ """
2
+ This module converts Timetree events to iCal format.
3
+ It is intended to be used with the Timetree API response files.
4
+ https://timetreeapp.com/api/v1/calendar/{calendar_id}/events/sync
5
+ """
6
+
7
+ import argparse
8
+ from icalendar import Calendar
9
+ from timetree_exporter import TimeTreeEvent, ICalEventFormatter
10
+ from timetree_exporter.utils import get_events_from_file, paths_to_filelist
11
+
12
+ # Parse arguments
13
+ parser = argparse.ArgumentParser(
14
+ description="Convert Timetree events to iCal format",
15
+ prog="timetree_exporter",
16
+ )
17
+ parser.add_argument(
18
+ "input",
19
+ type=str,
20
+ help="Path to the Timetree response file(s)/folder",
21
+ nargs="+",
22
+ )
23
+ parser.add_argument(
24
+ "-o",
25
+ "--output",
26
+ type=str,
27
+ help="Path to the output iCal file",
28
+ default="timetree.ics",
29
+ )
30
+ args = parser.parse_args()
31
+
32
+
33
+ cal = Calendar()
34
+ filenames = paths_to_filelist(args.input)
35
+
36
+ for filename in filenames:
37
+ # Skip non-JSON files
38
+ if not filename.endswith(".json"):
39
+ print(f"Skipping {filename} (Invalid file type, should be .json)")
40
+ continue
41
+ print(f"Parsing {filename}")
42
+
43
+ # Get events from file
44
+ events = get_events_from_file(filename)
45
+ if events is None:
46
+ continue
47
+
48
+ # Add events to calendar
49
+ for event in events:
50
+ time_tree_event = TimeTreeEvent.from_dict(event)
51
+ formatter = ICalEventFormatter(time_tree_event)
52
+ iCalEvent = formatter.to_ical()
53
+ cal.add_component(iCalEvent)
54
+
55
+ # Write calendar to file
56
+ with open(args.output, "wb") as f: # Path Traversal Vulnerability if on a server
57
+ f.write(cal.to_ical())
@@ -0,0 +1,70 @@
1
+ """This module provides the TimeTreeEvent class for representing TimeTree events."""
2
+
3
+
4
+ class TimeTreeEvent:
5
+ """TimeTree event class"""
6
+
7
+ # pylint: disable=too-many-instance-attributes
8
+
9
+ def __init__(
10
+ self,
11
+ uuid: str,
12
+ title: str,
13
+ created_at: int,
14
+ updated_at: int,
15
+ recurrences: list,
16
+ alerts: list,
17
+ url: str,
18
+ note: str,
19
+ start_at: int,
20
+ end_at: int,
21
+ all_day: bool,
22
+ start_timezone: str,
23
+ end_timezone: str,
24
+ location_lat: str,
25
+ location_lon: str,
26
+ location: str,
27
+ ):
28
+ # pylint: disable=too-many-arguments
29
+ # pylint: disable=too-many-locals
30
+ self.uuid = uuid
31
+ self.title = title
32
+ self.created_at = created_at
33
+ self.updated_at = updated_at
34
+ self.note = note
35
+ self.location = location
36
+ self.location_lat = location_lat
37
+ self.location_lon = location_lon
38
+ self.url = url
39
+ self.start_at = start_at
40
+ self.start_timezone = start_timezone
41
+ self.end_at = end_at
42
+ self.end_timezone = end_timezone
43
+ self.all_day = all_day
44
+ self.alerts = alerts
45
+ self.recurrences = recurrences
46
+
47
+ @classmethod
48
+ def from_dict(cls, event_data: dict):
49
+ """Create TimeTreeEvent object from JSON data"""
50
+ return cls(
51
+ uuid=event_data.get("uuid"),
52
+ title=event_data.get("title"),
53
+ created_at=event_data.get("created_at"),
54
+ updated_at=event_data.get("updated_at"),
55
+ note=event_data.get("note"),
56
+ location=event_data.get("location"),
57
+ location_lat=event_data.get("location_lat"),
58
+ location_lon=event_data.get("location_lon"),
59
+ url=event_data.get("url"),
60
+ start_at=event_data.get("start_at"),
61
+ start_timezone=event_data.get("start_timezone"),
62
+ end_at=event_data.get("end_at"),
63
+ end_timezone=event_data.get("end_timezone"),
64
+ all_day=event_data.get("all_day"),
65
+ alerts=event_data.get("alerts"),
66
+ recurrences=event_data.get("recurrences"),
67
+ )
68
+
69
+ def __str__(self):
70
+ return self.title
@@ -0,0 +1,164 @@
1
+ """
2
+ This module provides the ICalEventFormatter class
3
+ for formatting TimeTree events into iCalendar format.
4
+ """
5
+
6
+ from datetime import datetime, timedelta
7
+ from icalendar import Event, vRecur, vDate, vDatetime, vGeo
8
+ from icalendar.prop import vDDDLists
9
+ from icalendar.parser import Contentline
10
+ from dateutil import tz
11
+ from timetree_exporter.event import TimeTreeEvent
12
+
13
+
14
+ class ICalEventFormatter:
15
+ """
16
+ Class for formatting TimeTree events into iCalendar format.
17
+ """
18
+
19
+ def __init__(self, time_tree_event: TimeTreeEvent):
20
+ self.time_tree_event = time_tree_event
21
+
22
+ @property
23
+ def uid(self):
24
+ """Return the UUID of the event."""
25
+ return self.time_tree_event.uuid
26
+
27
+ @property
28
+ def summary(self):
29
+ """Return the title of the event."""
30
+ return self.time_tree_event.title
31
+
32
+ @property
33
+ def created(self):
34
+ """Return the creation time of the event."""
35
+ return vDatetime(
36
+ datetime.fromtimestamp(
37
+ self.time_tree_event.created_at / 1000, tz.gettz("UTC")
38
+ )
39
+ )
40
+
41
+ @property
42
+ def last_modify(self):
43
+ """Return the last modification time of the event."""
44
+ return vDatetime(
45
+ datetime.fromtimestamp(
46
+ self.time_tree_event.updated_at / 1000, tz.gettz("UTC")
47
+ )
48
+ )
49
+
50
+ @property
51
+ def description(self):
52
+ """Return the note of the event."""
53
+ return self.time_tree_event.note if self.time_tree_event.note != "" else None
54
+
55
+ @property
56
+ def location(self):
57
+ """Return the location of the event."""
58
+ return (
59
+ self.time_tree_event.location
60
+ if self.time_tree_event.location != ""
61
+ else None
62
+ )
63
+
64
+ @property
65
+ def geo(self):
66
+ """Return the geolocation of the event."""
67
+ if (
68
+ self.time_tree_event.location_lat is None
69
+ or self.time_tree_event.location_lon is None
70
+ ):
71
+ return None
72
+ return vGeo(
73
+ (self.time_tree_event.location_lat, self.time_tree_event.location_lon)
74
+ )
75
+
76
+ @property
77
+ def url(self):
78
+ """Return the URL of the event."""
79
+ return self.time_tree_event.url if self.time_tree_event.url != "" else None
80
+
81
+ def get_datetime(self, is_start_time):
82
+ """Return the start or end time of the event."""
83
+ if is_start_time:
84
+ time = self.time_tree_event.start_at
85
+ timezone = self.time_tree_event.start_timezone
86
+ else:
87
+ time = self.time_tree_event.end_at
88
+ timezone = self.time_tree_event.end_timezone
89
+
90
+ if self.time_tree_event.all_day:
91
+ return vDate(
92
+ datetime.fromtimestamp(
93
+ time / 1000,
94
+ tz.gettz(timezone),
95
+ )
96
+ )
97
+ return vDatetime(
98
+ datetime.fromtimestamp(
99
+ time / 1000,
100
+ tz.gettz(timezone),
101
+ )
102
+ )
103
+
104
+ @property
105
+ def dtstart(self):
106
+ """Return the start time of the event."""
107
+ return self.get_datetime(is_start_time=True)
108
+
109
+ @property
110
+ def dtend(self):
111
+ """Return the end time of the event."""
112
+ return self.get_datetime(is_start_time=False)
113
+
114
+ @property
115
+ def alerts(self):
116
+ """Return the alerts of the event."""
117
+ alerts = []
118
+ for alert in self.time_tree_event.alerts:
119
+ alarm = Event()
120
+ alarm.add("action", "DISPLAY")
121
+ alarm.add("description", "Reminder")
122
+ alarm.add("trigger", timedelta(minutes=-alert))
123
+ alerts.append(alarm)
124
+ return alerts
125
+
126
+ def add_recurrences(self, event):
127
+ """Add recurrences to iCal event"""
128
+ for recurrence in self.time_tree_event.recurrences:
129
+ contentline = Contentline(recurrence)
130
+ name, parameters, value = contentline.parts()
131
+ if name.lower() == "rrule":
132
+ event.add(name, vRecur.from_ical(value), parameters)
133
+ elif name.lower() == "exdate" or name.lower() == "rdate":
134
+ event.add(name, vDDDLists.from_ical(value), parameters)
135
+ else:
136
+ raise ValueError(f"Unknown recurrence type: {name}")
137
+
138
+ def to_ical(self) -> Event:
139
+ """Return the iCal event."""
140
+ event = Event()
141
+
142
+ event.add("uid", self.uid)
143
+ event.add("summary", self.summary)
144
+ event.add("dtstamp", datetime.now(tz.tzutc()))
145
+ event.add("created", self.created)
146
+ event.add("last-modify", self.last_modify)
147
+ event.add("dtstart", self.dtstart)
148
+ event.add("dtend", self.dtend)
149
+
150
+ if self.location:
151
+ event.add("location", self.location)
152
+ if self.geo:
153
+ event.add("geo", self.geo)
154
+ if self.url:
155
+ event.add("url", self.url)
156
+ if self.description:
157
+ event.add("description", self.description)
158
+
159
+ for alert in self.alerts:
160
+ event.add_component(alert)
161
+
162
+ self.add_recurrences(event)
163
+
164
+ return event
@@ -0,0 +1,28 @@
1
+ """Utility functions for Timetree Exporter"""
2
+
3
+ import json
4
+ import os
5
+
6
+
7
+ def get_events_from_file(file_path) -> list:
8
+ """Fetch events from Timetree response file"""
9
+ try:
10
+ with open(file_path, "r", encoding="UTF-8") as response_file:
11
+ response_data = json.load(response_file)
12
+ return response_data["events"]
13
+ except FileNotFoundError:
14
+ print(f"File not found: {file_path}")
15
+ return None
16
+
17
+
18
+ def paths_to_filelist(paths: list) -> list:
19
+ """Converts a list of paths to a list of files"""
20
+ filenames = []
21
+ for path in paths:
22
+ if os.path.isdir(path):
23
+ filenames += [os.path.join(path, file) for file in os.listdir(path)]
24
+ elif os.path.isfile(path):
25
+ filenames.append(path)
26
+ else:
27
+ print(f"Invalid path: {path}")
28
+ return filenames
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.1
2
+ Name: timetree-exporter
3
+ Version: 0.1.0
4
+ Summary: A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics)
5
+ Author-email: Fong-Chun Tsai <eoleedimin@gmail.com>
6
+ Project-URL: Homepage, https://github.com/eoleedi/TimeTree-Exporter
7
+ Project-URL: Repository, https://github.com/eoleedi/TimeTree-Exporter
8
+ Project-URL: Issues, https://github.com/eoleedi/TimeTree-Exporter/issues
9
+ Project-URL: Changelog, https://github.com/eoleedi/TimeTree-Exporter/CHANGELOG.md
10
+ Keywords: timetree,exporter,icalendar,ics
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: icalendar
18
+
19
+ # TimeTree Exporter
20
+ A Tool for Exporting TimeTree Calendar and Convert to iCal format(.ics) \
21
+ (The .ics file can then be imported into other calendar apps such as Google Calendar, Apple Calendar, Outlook Calendar, etc.)
22
+
23
+ # Usage
24
+
25
+ First, download the timetree data manually and put it in the responses folder(see below)
26
+
27
+ ## Method 1: Pip
28
+ 1. Install the package `pip3 install timetree-exporter`
29
+ 2. Run `timetree-exporter <path-to-responses-folder>`
30
+ 3. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
31
+
32
+ ## Method 2: Cloning the repository
33
+ 1. Clone the repository `git clone https://github.com/eoleedi/TimeTree-exporter.git`
34
+ 2. Change the directory to the project folder `cd TimeTree-exporter`
35
+ 3. Install the requirements `pip3 install -r requirements.txt`
36
+ 4. Run `python3 -m timetree_exporter responses`
37
+ 5. A timetree.ics file will be generated in the same directory, then you can import it to your calendar app.
38
+
39
+ ## How to download timetree data
40
+ 1. Go to [https://timetreeapp.com/signin](https://timetreeapp.com/signin)
41
+ 2. Open the developer tools before logging in
42
+ > Can be opened by pressing F12
43
+ 3. Go to the network tab
44
+ > Remember to Press Ctrl + R to start recording
45
+ 4. Turn on preserve log
46
+ 5. Type in "sync" in the filter box
47
+ 6. Log in
48
+ 7. Click on the calendar you want to export
49
+ 8. You will see couple of requests with the name "sync"
50
+ > There seems to be a maximum of 300 events in single requests. \
51
+ > If you have more than 300 events, you will see multiple requests with the name "sync"
52
+ 9. Right click on the request and select "Copy Response"
53
+ 10. Paste it into a json file under the response folder under this project (etc. sync.json)
54
+ > The file name should end with .json
55
+ 11. Do the same thing for all the requests with steps 7~10
56
+
57
+ ![Prepare for signin: Step 1~5](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/prepare-for-signin.png)
58
+ *Prepare for signin: Step 1~5*
59
+ ![Copy response: Step 8~9](https://github.com/eoleedi/TimeTree-exporter/raw/main/assets/images/copy-response.png)
60
+ *Copy response: Step 8~9*
61
+
62
+ # Advanced Usage
63
+ You can also specify the output file name by running `python3 -m timetree_exporter -o <output_file_name>.ics` or `timetree-exporter -o <output_file_name>.ics`
64
+
65
+ # Recommendation
66
+ You are recommended to import the ics file into a separate calendar (take google calendar as an example) as if anything goes wrong, you can just delete the calendar and reimport it.
67
+
68
+ # Requirements
69
+ icalendar==5.0.12
70
+
71
+ # Limitations
72
+ Currently, TimeTree data can only be downloaded manually through a web browser.
73
+
74
+ # Roadmap of the properties mapping to iCal
75
+ - [ ] **ID**
76
+ - [ ] **Primary ID**
77
+ - [ ] **Calendar ID**
78
+ - [x] **UUID**
79
+ - [ ] **Category**
80
+ - [ ] **Type**
81
+ - [ ] **Author ID**
82
+ - [ ] **Author Type**
83
+ - [x] **Title**
84
+ - [x] **All Day**
85
+ - [x] **Start At**
86
+ - [x] **Start Timezone**
87
+ - [x] **End At**
88
+ - [x] **End Timezone**
89
+ - [ ] **Label ID**
90
+ - [x] **Location**
91
+ - [x] **Location Latitude**
92
+ - [x] **Location Longitude**
93
+ - [x] **URL**
94
+ - [x] **Note**
95
+ - [ ] **Lunar**
96
+ - [ ] **Attendees**
97
+ - [x] **Recurrences**
98
+ - [ ] **Recurring UUID**
99
+ - [x] **Alerts**
100
+ - [ ] **Parent ID**
101
+ - [ ] **Link Object ID**
102
+ - [ ] **Link Object ID String**
103
+ - [ ] **Row Order**
104
+ - [ ] **Attachment**
105
+ - [ ] **Like Count**
106
+ - [ ] **Files**
107
+ - [ ] **Deactivated At**
108
+ - [ ] **Pinned At**
109
+ - [x] **Updated At**
110
+ - [x] **Created At**
@@ -0,0 +1,25 @@
1
+ .gitignore
2
+ CHANGELOG.md
3
+ LICENSE
4
+ README.md
5
+ pyproject.toml
6
+ requirements.txt
7
+ .github/dependabot.yml
8
+ .github/workflows/pylint.yml
9
+ .github/workflows/python-publish.yml
10
+ .github/workflows/release-please.yml
11
+ .vscode/settings.json
12
+ assets/images/copy-response.png
13
+ assets/images/prepare-for-signin.png
14
+ responses/README.md
15
+ timetree_exporter/__init__.py
16
+ timetree_exporter/__main__.py
17
+ timetree_exporter/event.py
18
+ timetree_exporter/formatter.py
19
+ timetree_exporter/utils.py
20
+ timetree_exporter.egg-info/PKG-INFO
21
+ timetree_exporter.egg-info/SOURCES.txt
22
+ timetree_exporter.egg-info/dependency_links.txt
23
+ timetree_exporter.egg-info/entry_points.txt
24
+ timetree_exporter.egg-info/requires.txt
25
+ timetree_exporter.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ timetree-exporter = timetree_exporter.__main__:main
@@ -0,0 +1 @@
1
+ timetree_exporter