"""User photo and profile management. Part of the _userop_tmp sub-package.""" """GAM user operations: Looker Studio, user groups, licenses, photos, profile, sheets, tokens, deprovision.""" import re import sys import googleapiclient.http import base64 import os import google.auth import google.auth.exceptions from gamlib import glaction from gamlib import glapi as API from gamlib import glcfg as GC from gamlib import glclargs from gamlib import glentity from gamlib import glgapi as GAPI from gamlib import glglobals as GM from gamlib import glindent from gamlib import glmsgs as Msg Act = glaction.GamAction() Ent = glentity.GamEntity() Ind = glindent.GamIndent() Cmd = glclargs.GamCLArgs() def _getMain(): return sys.modules['gam'] def __getattr__(name): """Fall back to gam module for any undefined names.""" main = _getMain() try: return getattr(main, name) except AttributeError: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from tempfile import TemporaryFile def updatePhoto(users): cd = _getMain().buildGAPIObject(API.DIRECTORY) baseFileIdEntity = drive = owner = None sourceFolder = os.getcwd() if Cmd.NumArgumentsRemaining() == 1: filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN) else: filenamePattern = '#email#.jpg' while Cmd.ArgumentsRemaining(): myarg = _getMain().getArgument() if myarg == 'drivedir': sourceFolder = GC.Values[GC.DRIVE_DIR] elif myarg == 'sourcefolder': sourceFolder = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.INPUT_DIR) if not os.path.isdir(sourceFolder): _getMain().entityDoesNotExistExit(Ent.DIRECTORY, sourceFolder) elif myarg == 'filename': filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN) elif myarg == 'gphoto': owner, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, _getMain().getEmailAddress()) if not drive: return baseFileIdEntity = _getMain().getDriveFileEntity(queryShortcutsOK=False) else: _getMain().unknownArgumentExit() p = re.compile('^(ht|f)tps?://.*$') i, count, users = _getMain().getEntityArgument(users) for user in users: i += 1 user, userName, _ = _getMain().splitEmailAddressOrUID(user) filename = _getMain()._substituteForUser(filenamePattern, user, userName) if baseFileIdEntity is not None: fileIdEntity = baseFileIdEntity.copy() if fileIdEntity['query'] is not None: fileIdEntity['query'] = _substituteForUser(fileIdEntity['query'], user, userName) _, _, jcount = _getMain()._validateUserGetFileIDs(owner, 0, 0, fileIdEntity, drive=drive, entityType=None) if jcount == 0: _getMain().entityItemValueListActionNotPerformedWarning([Ent.USER, user], [Ent.OWNER, owner], Msg.NO_ENTITIES_FOUND.format(Ent.Singular(Ent.DRIVE_FILE)), i, count) continue if jcount > 1: _getMain().entityItemValueListActionNotPerformedWarning([Ent.USER, user], [Ent.OWNER, owner], Msg.MULTIPLE_ENTITIES_FOUND.format(Ent.Plural(Ent.DRIVE_FILE), jcount, ','.join(fileIdEntity['list'])), i, count) continue fb = TemporaryFile(mode='wb+') filename = fileIdEntity['list'][0] request = drive.files().get_media(fileId=filename) downloader = googleapiclient.http.MediaIoBaseDownload(fb, request) done = False while not done: _, done = downloader.next_chunk() fb.seek(0) image_data = fb.read() fb.close() elif p.match(filename): try: status, image_data = _getMain().getHttpObj().request(filename, 'GET') if status['status'] != '200': _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], Msg.NOT_ALLOWED, i, count) continue except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count) continue else: filename = os.path.join(sourceFolder, filename) try: with open(filename, 'rb') as f: image_data = f.read() except (OSError, IOError) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count) continue body = {'photoData': base64.urlsafe_b64encode(image_data).decode(_getMain().UTF8)} try: try: _getMain().callGAPI(cd.users().photos(), 'delete', bailOnInternalError=True, throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND, GAPI.INTERNAL_ERROR], userKey=user) except (GAPI.photoNotFound, GAPI.internalError): pass _getMain().callGAPI(cd.users().photos(), 'update', throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_INPUT, GAPI.CONDITION_NOT_MET], userKey=user, body=body, fields='') _getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, filename], i, count) except (GAPI.invalidInput, GAPI.conditionNotMet) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count) except (GAPI.userNotFound, GAPI.forbidden): _getMain().entityUnknownWarning(Ent.USER, user, i, count) # gam delete photo def deletePhoto(users): cd = _getMain().buildGAPIObject(API.DIRECTORY) _getMain().checkForExtraneousArguments() i, count, users = _getMain().getEntityArgument(users) for user in users: i += 1 user = _getMain().normalizeEmailAddressOrUID(user) try: _getMain().callGAPI(cd.users().photos(), 'delete', bailOnInternalError=True, throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND, GAPI.INTERNAL_ERROR], userKey=user) _getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, ''], i, count) except (GAPI.photoNotFound, GAPI.internalError) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, ''], str(e), i, count) except (GAPI.userNotFound, GAPI.forbidden): _getMain().entityUnknownWarning(Ent.USER, user, i, count) def getPhoto(users, profileMode): cd = _getMain().buildGAPIObject(API.DIRECTORY) targetFolder = os.getcwd() filenamePattern = '#email#.#ext#' noDefault = returnURLonly = False writeFileData = showPhotoData = True size = '' while Cmd.ArgumentsRemaining(): myarg = _getMain().getArgument() if myarg == 'drivedir': targetFolder = GC.Values[GC.DRIVE_DIR] elif myarg == 'targetfolder': targetFolder = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.DRIVE_DIR) if not os.path.isdir(targetFolder): os.makedirs(targetFolder) elif myarg == 'filename': filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN) elif myarg == 'nofile': writeFileData = False elif myarg == 'noshow': showPhotoData = False elif profileMode and myarg == 'returnurlonly': returnURLonly = True elif myarg == 'nodefault': noDefault = True elif profileMode and myarg == 'size': size = _getMain().getInteger(minVal=50) else: _getMain().unknownArgumentExit() i, count, users = _getMain().getEntityArgument(users) for user in users: i += 1 if profileMode: user, people = _getMain().buildGAPIServiceObject(API.PEOPLE, user, i, count) if not people: continue else: user = _getMain().normalizeEmailAddressOrUID(user) _, userName, _ = _getMain().splitEmailAddressOrUID(user) filename = os.path.join(targetFolder, _getMain()._substituteForUser(filenamePattern, user, userName)) try: if not showPhotoData: _getMain().entityPerformActionNumItems([Ent.USER, user], 1, Ent.PHOTO, i, count) if not profileMode: photo = _getMain().callGAPI(cd.users().photos(), 'get', throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND], userKey=user) if showPhotoData: _getMain().writeStdout(photo['photoData']+'\n') photo_data = base64.urlsafe_b64decode(photo['photoData']) else: result = _getMain().callGAPI(people.people(), 'get', throwReasons=[GAPI.NOT_FOUND], retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, resourceName='people/me', personFields='photos') default = False url = None for photo in result.get('photos', []): if photo['metadata']['source']['type'] == 'PROFILE': default = photo.get('default', False) url = photo['url'] break if not url: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], Msg.PROFILE_PHOTO_NOT_FOUND, i, count) continue if noDefault and default: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], Msg.PROFILE_PHOTO_IS_DEFAULT, i, count) continue if returnURLonly: _getMain().writeStdout(f'{url}\n') continue if size: url = re.sub(r"=s\d+$", f"=s{size}", url) try: status, photo_data = _getMain().getHttpObj().request(url, 'GET') if status['status'] != '200': _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], Msg.NOT_ALLOWED, i, count) continue if showPhotoData: _getMain().writeStdout(base64.encodebytes(photo_data).decode(_getMain().UTF8)) except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count) continue if writeFileData: if photo_data[:3] == b'\xff\xd8\xff': extension = 'jpg' elif photo_data[:8] == b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a': extension = 'png' elif photo_data[:6] == b'\x47\x49\x46\x38\x37\x61' or photo_data[:6] == b'\x47\x49\x46\x38\x39\x61': extension = 'gif' elif photo_data[:2] == b'\x42\x4d': extension= 'bmp' elif photo_data[:4] == b'\x49\x49\x2A\x00' or photo_data[:4] == b'\x4D\x4D\x00\x2A': extension= 'tif' else: extension = 'img' filenameExt = filename.replace('#ext#', extension) status, e = _getMain().writeFileReturnError(filenameExt, photo_data, mode='wb') if status: if not showPhotoData: _getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, filenameExt], i, count) else: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filenameExt], str(e), i, count) except (GAPI.notFound, GAPI.photoNotFound) as e: _getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], str(e), i, count) except (GAPI.userNotFound, GAPI.forbidden): _getMain().entityUnknownWarning(Ent.USER, user, i, count) # gam get photo [drivedir|(targetfolder )] [filename ]] # [noshow] [nofile] def getUserPhoto(users): getPhoto(users, False) # gam get profilephoto [drivedir|(targetfolder )] [filename ] # [noshow] [nofile] [returnurlonly] [nodefault] [size ] def getProfilePhoto(users): getPhoto(users, True) PROFILE_SHARING_CHOICE_MAP = { 'share': True, 'shared': True, 'unshare': False, 'unshared': False, } def _setShowProfile(users, function, **kwargs): cd = _getMain().buildGAPIObject(API.DIRECTORY) _getMain().checkForExtraneousArguments() i, count, users = _getMain().getEntityArgument(users) for user in users: i += 1 user = _getMain().normalizeEmailAddressOrUID(user) try: result = _getMain().callGAPI(cd.users(), function, throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN], userKey=user, fields='includeInGlobalAddressList', **kwargs) _getMain().printEntity([Ent.USER, user, Ent.PROFILE_SHARING_ENABLED, result.get('includeInGlobalAddressList', _getMain().UNKNOWN)], i, count) except (GAPI.userNotFound, GAPI.forbidden): _getMain().entityUnknownWarning(Ent.USER, user, i, count) # gam profile share|shared|unshare|unshared def setProfile(users): body = {'includeInGlobalAddressList': _getMain().getChoice(PROFILE_SHARING_CHOICE_MAP, mapChoice=True)} _setShowProfile(users, 'update', body=body) # gam show profile def showProfile(users): _setShowProfile(users, 'get') # gam create sheet # ((json [charset ] ) | # (json file [charset ])) # [] # [formatjson] [returnidonly]