1
|
1 #!/usr/bin/python
|
3
|
2 # coding=utf-8
|
2
|
3 ###
|
|
4 ### Google Calendar MultiMerge v0.000001
|
|
5 ### (C) 2016 Matti 'ccr' Hamalainen <ccr@tnsp.org>
|
|
6 ###
|
|
7 ### Python 2.7 <= x < 3 required! Please refer to
|
|
8 ### README.txt for information on other depencies.
|
|
9 ###
|
1
|
10 import os
|
|
11 import sys
|
|
12 import signal
|
|
13 import re
|
|
14 import time
|
|
15 import datetime
|
|
16 import httplib2
|
|
17 import ConfigParser
|
|
18 import oauth2client
|
|
19 from oauth2client import client
|
|
20 from oauth2client import tools
|
4
|
21 from oauth2client import file
|
1
|
22 from googleapiclient import discovery
|
|
23
|
|
24
|
|
25 ###
|
|
26 ### Misc. helper functions
|
|
27 ###
|
|
28
|
|
29 ## Wrapper for print() that does not break when redirecting stdin/out
|
|
30 ## because of piped output not having a defined encoding. We default
|
|
31 ## to UTF-8 encoding in output here.
|
|
32 def gcm_print(smsg):
|
|
33 gcm_msgbuf.append(smsg.encode("UTF-8"))
|
|
34 if sys.stdout.encoding != None:
|
|
35 print(smsg.encode(sys.stdout.encoding))
|
|
36 else:
|
|
37 print(smsg.encode("UTF-8"))
|
|
38
|
|
39
|
|
40 ## Fatal errors
|
|
41 def gcm_fatal(smsg):
|
|
42 gcm_print(u"ERROR: "+ smsg)
|
|
43 sys.exit(1)
|
|
44
|
|
45
|
|
46 ## Debug messages
|
|
47 def gcm_debug(smsg):
|
|
48 if cfg.debug:
|
|
49 gcm_print(u"DBG: "+ smsg)
|
|
50 else:
|
|
51 gcm_msgbuf.append(u"DBG: "+ smsg.encode("UTF-8"))
|
|
52
|
|
53
|
|
54 ## Handle SIGINT signals here
|
|
55 def gcm_signal_handler(signal, frame):
|
|
56 gcm_print("\nQuitting due to SIGINT / Ctrl+C!")
|
|
57 sys.exit(0)
|
|
58
|
|
59
|
|
60 def gcm_get_credentials(mcfg):
|
|
61 store = oauth2client.file.Storage(mcfg.credential_file)
|
|
62 credentials = store.get()
|
|
63 if not credentials or credentials.invalid:
|
|
64 flow = client.flow_from_clientsecrets(mcfg.secret_file, mcfg.scope)
|
|
65 flow.user_agent = mcfg.app_name
|
|
66 credentials = tools.run_flow(flow, store, mcfg)
|
|
67 if not credentials or credentials.invalid:
|
|
68 gcm_fatal("Failed to authenticate / invalid credentials.")
|
|
69 return credentials
|
|
70
|
|
71
|
|
72 def gcm_dump_events(events):
|
|
73 for event in events:
|
|
74 ev_start = event["start"].get("dateTime", event["start"].get("date"))
|
|
75 ev_end = event["end"].get("dateTime", event["end"].get("date"))
|
|
76 gcm_print(u"{0:25} - {1:25} : {2}".format(ev_start, ev_end, event["summary"]))
|
|
77
|
|
78
|
|
79 class GCMSettings(dict):
|
|
80 def __init__(self):
|
|
81 self.m_data = {}
|
|
82 self.m_saveable = {}
|
|
83 self.m_translate = {}
|
|
84
|
|
85 def __getattr__(self, name):
|
|
86 if name in self.m_data:
|
|
87 return self.m_data[name]
|
|
88 else:
|
|
89 gcm_fatal("GCMSettings.__getattr__(): No such attribute '"+ name +"'.")
|
|
90
|
|
91 def mtranslate(self, name, value):
|
|
92 if name in self.m_translate and self.m_translate[name]:
|
|
93 return self.m_translate[name](value)
|
|
94 else:
|
|
95 return value
|
|
96
|
|
97 def mdef(self, name, saveable, validate, translate, value):
|
|
98 self.m_saveable[name] = saveable
|
|
99 self.m_data[name] = self.mtranslate(name, value)
|
|
100
|
|
101 def mset(self, name, value):
|
|
102 if name in self.m_data:
|
|
103 self.m_data[name] = self.mtranslate(name, value)
|
|
104 else:
|
|
105 gcm_fatal("GCMSettings.mset(): No such attribute '"+ name +"'.")
|
|
106
|
|
107 def mget(self, name):
|
|
108 if name in self.m_data:
|
|
109 return self.m_data[name]
|
|
110 else:
|
|
111 return None
|
|
112
|
|
113
|
|
114 ###
|
|
115 ### Main program starts
|
|
116 ###
|
|
117 gcm_msgbuf = []
|
|
118 signal.signal(signal.SIGINT, gcm_signal_handler)
|
|
119
|
|
120
|
|
121 ## Settings
|
|
122 cfg = GCMSettings()
|
|
123
|
|
124 cfg.mdef("debug", True, gcm_is_bool, gcm_trans_bool, False)
|
|
125
|
|
126 cfg.mdef("source_regex", True, gcm_is_string, None, "^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$")
|
|
127 cfg.mdef("source_regmap", False, gcm_is_list, gcm_trans_list, [1, 2])
|
|
128 cfg.mdef("source_regmap_len", False, None, None, len(cfg.source_regmap))
|
|
129
|
|
130 cfg.mdef("dest_name", True, gcm_is_string, None, u"Raahen kansainvälisyystoiminta")
|
|
131 cfg.mdef("dest_id", True, gcm_is_string, None, None)
|
|
132
|
|
133 cfg.mdef("noauth_local_webserver", False, None, None, True)
|
|
134 #cfg.mdef("auth_host_name", False, None, None, "localhost")
|
|
135 #cfg.mdef("auth_host_port", False, None, None, [8080, 8090])
|
|
136 cfg.mdef("logging_level", True, gcm_is_log_level, gcm_trans_log_level, "ERROR")
|
|
137
|
|
138 # No need to touch these
|
|
139 cfg.mdef("app_name", False, None, None, "Google Calendar MultiMerge")
|
|
140 cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar")
|
|
141 #cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly")
|
|
142 cfg.mdef("secret_file", True, gcm_is_filename, None, "client_secret.json")
|
|
143 cfg.mdef("credential_file", True, gcm_is_filename, None, "client_credentials.json")
|
|
144
|
|
145 ## Initialize and authorize API connection
|
|
146 credentials = gcm_get_credentials(cfg)
|
|
147 http = credentials.authorize(httplib2.Http())
|
|
148 service = discovery.build("calendar", "v3", http=http)
|
|
149
|
|
150
|
|
151 ## Fetch complete calendar list
|
|
152 gcm_debug("Fetching available calendars ..")
|
|
153 calendars = []
|
|
154 calPageToken = None
|
|
155 while True:
|
|
156 # We want everything except deleted and hidden calendars
|
|
157 calResult = service.calendarList().list(
|
|
158 showHidden=False,
|
|
159 showDeleted=False,
|
|
160 pageToken=calPageToken
|
|
161 ).execute()
|
|
162
|
|
163 calendars.extend(calResult.get("items", []))
|
|
164 calPageToken = calResult.get("nextPageToken")
|
|
165 if not calPageToken:
|
|
166 break
|
|
167
|
|
168 if len(calendars) == 0:
|
|
169 gcm_fatal("No calendars found?")
|
|
170
|
|
171
|