xref: /aosp_15_r20/external/autotest/site_utils/gmail_lib.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li# Copyright 2015 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""
7*9c5db199SXin LiMail the content of standard input.
8*9c5db199SXin Li
9*9c5db199SXin LiExample usage:
10*9c5db199SXin Li  Use pipe:
11*9c5db199SXin Li     $ echo "Some content" |./gmail_lib.py -s "subject" [email protected] [email protected]
12*9c5db199SXin Li
13*9c5db199SXin Li  Manually input:
14*9c5db199SXin Li     $ ./gmail_lib.py -s "subject" [email protected] [email protected]
15*9c5db199SXin Li     > Line 1
16*9c5db199SXin Li     > Line 2
17*9c5db199SXin Li     Ctrl-D to end standard input.
18*9c5db199SXin Li"""
19*9c5db199SXin Lifrom __future__ import absolute_import
20*9c5db199SXin Lifrom __future__ import division
21*9c5db199SXin Lifrom __future__ import print_function
22*9c5db199SXin Li
23*9c5db199SXin Liimport argparse
24*9c5db199SXin Liimport base64
25*9c5db199SXin Liimport httplib2
26*9c5db199SXin Liimport logging
27*9c5db199SXin Liimport sys
28*9c5db199SXin Liimport random
29*9c5db199SXin Lifrom email.mime.text import MIMEText
30*9c5db199SXin Li
31*9c5db199SXin Liimport common
32*9c5db199SXin Lifrom autotest_lib.server import site_utils
33*9c5db199SXin Li
34*9c5db199SXin Litry:
35*9c5db199SXin Li    from apiclient.discovery import build as apiclient_build
36*9c5db199SXin Li    from apiclient import errors as apiclient_errors
37*9c5db199SXin Li    from oauth2client import file as oauth_client_fileio
38*9c5db199SXin Liexcept ImportError as e:
39*9c5db199SXin Li    apiclient_build = None
40*9c5db199SXin Li    logging.debug("API client for gmail disabled. %s", e)
41*9c5db199SXin Li
42*9c5db199SXin Li
43*9c5db199SXin LiRETRY_DELAY = 5
44*9c5db199SXin LiRETRY_BACKOFF_FACTOR = 1.5
45*9c5db199SXin LiMAX_RETRY = 10
46*9c5db199SXin LiRETRIABLE_MSGS = [
47*9c5db199SXin Li        # User-rate limit exceeded
48*9c5db199SXin Li        r'HttpError 429',]
49*9c5db199SXin Li
50*9c5db199SXin Liclass GmailApiException(Exception):
51*9c5db199SXin Li    """Exception raised in accessing Gmail API."""
52*9c5db199SXin Li
53*9c5db199SXin Li
54*9c5db199SXin Liclass Message():
55*9c5db199SXin Li    """An email message."""
56*9c5db199SXin Li
57*9c5db199SXin Li    def __init__(self, to, subject, message_text):
58*9c5db199SXin Li        """Initialize a message.
59*9c5db199SXin Li
60*9c5db199SXin Li        @param to: The recievers saperated by comma.
61*9c5db199SXin Li                   e.g. '[email protected],[email protected]'
62*9c5db199SXin Li        @param subject: String, subject of the message
63*9c5db199SXin Li        @param message_text: String, content of the message.
64*9c5db199SXin Li        """
65*9c5db199SXin Li        self.to = to
66*9c5db199SXin Li        self.subject = subject
67*9c5db199SXin Li        self.message_text = message_text
68*9c5db199SXin Li
69*9c5db199SXin Li
70*9c5db199SXin Li    def get_payload(self):
71*9c5db199SXin Li        """Get the payload that can be sent to the Gmail API.
72*9c5db199SXin Li
73*9c5db199SXin Li        @return: A dictionary representing the message.
74*9c5db199SXin Li        """
75*9c5db199SXin Li        message = MIMEText(self.message_text)
76*9c5db199SXin Li        message['to'] = self.to
77*9c5db199SXin Li        message['subject'] = self.subject
78*9c5db199SXin Li        return {'raw': base64.urlsafe_b64encode(message.as_string())}
79*9c5db199SXin Li
80*9c5db199SXin Li
81*9c5db199SXin Liclass GmailApiClient():
82*9c5db199SXin Li    """Client that talks to Gmail API."""
83*9c5db199SXin Li
84*9c5db199SXin Li    def __init__(self, oauth_credentials):
85*9c5db199SXin Li        """Init Gmail API client
86*9c5db199SXin Li
87*9c5db199SXin Li        @param oauth_credentials: Path to the oauth credential token.
88*9c5db199SXin Li        """
89*9c5db199SXin Li        if not apiclient_build:
90*9c5db199SXin Li            raise GmailApiException('Cannot get apiclient library.')
91*9c5db199SXin Li
92*9c5db199SXin Li        storage = oauth_client_fileio.Storage(oauth_credentials)
93*9c5db199SXin Li        credentials = storage.get()
94*9c5db199SXin Li        if not credentials or credentials.invalid:
95*9c5db199SXin Li            raise GmailApiException('Invalid credentials for Gmail API, '
96*9c5db199SXin Li                                    'could not send email.')
97*9c5db199SXin Li        http = credentials.authorize(httplib2.Http())
98*9c5db199SXin Li        self._service = apiclient_build('gmail', 'v1', http=http)
99*9c5db199SXin Li
100*9c5db199SXin Li
101*9c5db199SXin Li    def send_message(self, message, ignore_error=True):
102*9c5db199SXin Li        """Send an email message.
103*9c5db199SXin Li
104*9c5db199SXin Li        @param message: Message to be sent.
105*9c5db199SXin Li        @param ignore_error: If True, will ignore any HttpError.
106*9c5db199SXin Li        """
107*9c5db199SXin Li        try:
108*9c5db199SXin Li            # 'me' represents the default authorized user.
109*9c5db199SXin Li            message = self._service.users().messages().send(
110*9c5db199SXin Li                    userId='me', body=message.get_payload()).execute()
111*9c5db199SXin Li            logging.debug('Email sent: %s' , message['id'])
112*9c5db199SXin Li        except apiclient_errors.HttpError as error:
113*9c5db199SXin Li            if ignore_error:
114*9c5db199SXin Li                logging.error('Failed to send email: %s', error)
115*9c5db199SXin Li            else:
116*9c5db199SXin Li                raise
117*9c5db199SXin Li
118*9c5db199SXin Li
119*9c5db199SXin Lidef send_email(to, subject, message_text, retry=True, creds_path=None):
120*9c5db199SXin Li    """Send email.
121*9c5db199SXin Li
122*9c5db199SXin Li    @param to: The recipients, separated by comma.
123*9c5db199SXin Li    @param subject: Subject of the email.
124*9c5db199SXin Li    @param message_text: Text to send.
125*9c5db199SXin Li    @param retry: If retry on retriable failures as defined in RETRIABLE_MSGS.
126*9c5db199SXin Li    @param creds_path: The credential path for gmail account, if None,
127*9c5db199SXin Li                       will use DEFAULT_CREDS_FILE.
128*9c5db199SXin Li    """
129*9c5db199SXin Li    # TODO(ayatane): Deprecated, not untangling imports now
130*9c5db199SXin Li    pass
131*9c5db199SXin Li
132*9c5db199SXin Li
133*9c5db199SXin Liif __name__ == '__main__':
134*9c5db199SXin Li    logging.basicConfig(level=logging.DEBUG)
135*9c5db199SXin Li    parser = argparse.ArgumentParser(
136*9c5db199SXin Li            description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
137*9c5db199SXin Li    parser.add_argument('-s', '--subject', type=str, dest='subject',
138*9c5db199SXin Li                        required=True, help='Subject of the mail')
139*9c5db199SXin Li    parser.add_argument('-p', type=float, dest='probability',
140*9c5db199SXin Li                        required=False, default=0,
141*9c5db199SXin Li                        help='(optional) per-addressee probability '
142*9c5db199SXin Li                             'with which to send email. If not specified '
143*9c5db199SXin Li                             'all addressees will receive message.')
144*9c5db199SXin Li    parser.add_argument('recipients', nargs='*',
145*9c5db199SXin Li                        help='Email addresses separated by space.')
146*9c5db199SXin Li    args = parser.parse_args()
147*9c5db199SXin Li    if not args.recipients or not args.subject:
148*9c5db199SXin Li        print('Requires both recipients and subject.')
149*9c5db199SXin Li        sys.exit(1)
150*9c5db199SXin Li
151*9c5db199SXin Li    message_text = sys.stdin.read()
152*9c5db199SXin Li
153*9c5db199SXin Li    if args.probability:
154*9c5db199SXin Li        recipients = []
155*9c5db199SXin Li        for r in args.recipients:
156*9c5db199SXin Li            if random.random() < args.probability:
157*9c5db199SXin Li                recipients.append(r)
158*9c5db199SXin Li        if recipients:
159*9c5db199SXin Li            print('Randomly selected recipients %s' % recipients)
160*9c5db199SXin Li        else:
161*9c5db199SXin Li            print('Random filtering removed all recipients. Sending nothing.')
162*9c5db199SXin Li            sys.exit(0)
163*9c5db199SXin Li    else:
164*9c5db199SXin Li        recipients = args.recipients
165*9c5db199SXin Li
166*9c5db199SXin Li
167*9c5db199SXin Li    with site_utils.SetupTsMonGlobalState('gmail_lib', short_lived=True):
168*9c5db199SXin Li        send_email(','.join(recipients), args.subject , message_text)
169