imip-agent

tools/invite.py

1267:7819b77d9330
2017-09-15 Paul Boddie Introduced a tentative means of classifying periods for suitable operations upon updating an event.
     1 #!/usr/bin/env python     2      3 """     4 Prepare an invitation message.     5      6 Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from datetime import datetime    23 from imiptools.data import get_address, make_uid, new_object    24 from imiptools.dates import get_datetime, get_datetime_item, \    25                             get_default_timezone \    26 from imiptools.period import Period    27 from imiptools.mail import Messenger    28 from os.path import split    29 import sys    30     31 def make_object(organisers, recipients, summaries, from_datetimes, to_datetimes,    32                 attending, tzids):    33     34     """    35     Make an event from the given 'organisers', 'recipients', 'summaries',    36     'from_datetimes', 'to_datetimes'. If 'attending' is set to a true value, the    37     organiser will be added to the attendees list. If 'tzids' is set, any given    38     timezone is used; otherwise the default timezone is used.    39     """    40     41     if len(organisers) != 1:    42         raise ValueError("An organiser must be specified. More than one is not permitted.")    43     44     if not recipients:    45         raise ValueError("Recipients must be specified.")    46     47     organiser = organisers[0]    48     49     # Create an event for the calendar with the organiser and attendee details.    50     51     e = new_object("VEVENT")    52     e["UID"] = [(make_uid(organiser), {})]    53     e["ORGANIZER"] = [(organiser, {})]    54     55     attendees = []    56     57     if attending:    58         attendees.append((organiser, {"PARTSTAT" : "ACCEPTED"}))    59     60     for recipient in recipients:    61         attendees.append((recipient, {"RSVP" : "TRUE"}))    62     63     e["ATTENDEE"] = attendees    64     65     # Obtain a timezone.    66     67     if len(tzids) > 1:    68         raise ValueError("Only one timezone identifier should be given.")    69     70     tzid = tzids and tzids[0] or get_default_timezone()    71     72     # Obtain the event periods converting them to datetimes.    73     74     if not from_datetimes:    75         raise ValueError("The event needs a start datetime.")    76     if not to_datetimes:    77         raise ValueError("The event needs an end datetime.")    78     79     periods = []    80     81     for from_datetime, to_datetime in zip(from_datetimes, to_datetimes):    82         periods.append(get_period(from_datetime, to_datetime, tzid))    83     84     # Sort the periods and convert them.    85     86     periods.sort()    87     dtstart, dtend = periods[0].start, periods[0].end    88     89     # Convert event details to iCalendar values and attributes.    90     91     dtstart, dtstart_attr = get_datetime_item(dtstart, tzid)    92     dtend, dtend_attr = get_datetime_item(dtend, tzid)    93     94     e["DTSTART"] = [(dtstart, dtstart_attr)]    95     e["DTEND"] = [(dtend, dtend_attr)]    96     97     # Add recurrences.    98     99     rdates = []   100    101     for period in periods[1:]:   102         dtstart, dtend = period.start, period.end   103         dtstart, dtstart_attr = get_datetime_item(dtstart, tzid)   104         dtend, dtend_attr = get_datetime_item(dtend, tzid)   105         rdates.append("%s/%s" % (dtstart, dtend))   106    107     if rdates:   108         rdate_attr = {"VALUE" : "PERIOD"}   109         if tzid:   110             rdate_attr["TZID"] = tzid   111         e["RDATE"] = [(rdates, rdate_attr)]   112    113     return e   114    115 def get_period(from_datetime, to_datetime, tzid):   116    117     """   118     Return a tuple containing datetimes for 'from_datetime' and 'to_datetime',   119     using 'tzid' to convert the datetime strings if specified.   120     """   121    122     if tzid:   123         attr = {"TZID" : tzid}   124     else:   125         attr = None   126    127     fd = get_datetime(from_datetime, attr)   128     td = get_datetime(to_datetime, attr)   129    130     if not fd:   131         raise ValueError("One of the start datetimes (%s) is not recognised." % from_datetime)   132    133     if not td:   134         raise ValueError("One of the end datetimes (%s) is not recognised." % to_datetime)   135    136     if isinstance(fd, datetime) and not isinstance(td, datetime) or \   137        not isinstance(fd, datetime) and isinstance(td, datetime):   138    139         raise ValueError("One period has a mixture of date and datetime: %s - %s" % (from_datetime, to_datetime))   140    141     if fd > td:   142         raise ValueError("One period has reversed datetimes: %s - %s" % (from_datetime, to_datetime))   143    144     return Period(fd, td, tzid)   145    146 # Main program.   147    148 if __name__ == "__main__":   149     if len(sys.argv) > 1 and sys.argv[1] == "--help":   150         print >>sys.stderr, """\   151 Usage: %s <organiser> -r <recipient>... -s <summary> \\   152           -f <from datetime> -t <to datetime> \\   153           [ -z <timezone identifier> ] \\   154           [ --not-attending ] \\   155           [ --send | --encode ]   156    157 Prepare an invitation message to be sent to the indicated recipients, using   158 the specified <summary>, <from datetime> and <to datetime> to define the event   159 involved.   160    161 Any <timezone identifier> sets the time zone of any non-UTC datetimes.   162    163 If --not-attending is specified, the organiser will not be added to the   164 attendees list.   165    166 If --send is specified, attempt to send a message to the recipient addresses   167 from the logged in user.   168    169 If --encode is specified, encode the message and write it out. The showmail.py   170 tool can be used to display this encoded output.   171    172 Otherwise, write the iCalendar event object out.   173 """ % split(sys.argv[0])[1]   174         sys.exit(1)   175    176     # Gather the information about the invitation.   177    178     organisers = []   179     recipients = []   180     summaries = []   181     from_datetimes = []   182     to_datetimes = []   183     tzids = []   184     send = False   185     encode = False   186     attending = True   187    188     l = organisers   189    190     for arg in sys.argv[1:]:   191         if arg == "-r":   192             l = recipients   193         elif arg == "-s":   194             l = summaries   195         elif arg == "-f":   196             l = from_datetimes   197         elif arg == "-t":   198             l = to_datetimes   199         elif arg == "-z":   200             l = tzids   201         elif arg == "--send":   202             send = True   203             l = []   204         elif arg == "--encode":   205             encode = True   206             l = []   207         elif arg == "--not-attending":   208             attending = False   209             l = []   210         else:   211             l.append(arg)   212    213     # Attempt to construct the invitation.   214    215     try:   216         obj = make_object(organisers, recipients, summaries, from_datetimes,   217                           to_datetimes, attending, tzids)   218     except ValueError, exc:   219         print >>sys.stderr, """\   220 The invitation could not be prepared due to a problem with the following   221 details:   222    223 %s   224 """ % exc.message   225         sys.exit(1)   226    227     # Produce the invitation output.   228    229     if send or encode:   230         part = obj.to_part("REQUEST")   231    232         # Create a message and send it.   233    234         if send:   235             recipients = map(get_address, recipients)   236             messenger = Messenger()   237             msg = messenger.make_outgoing_message([part], recipients)   238             messenger.sendmail(recipients, msg.as_string())   239    240         # Output the encoded object.   241    242         else:   243             print msg.as_string()   244    245     # Output the object.   246    247     else:   248         print obj.to_string()   249    250 # vim: tabstop=4 expandtab shiftwidth=4