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