123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- from __future__ import print_function
- import os.path
- import google.auth
- import datetime as dt
- import math
- from google.auth.transport.requests import Request
- from google.oauth2.credentials import Credentials
- from google_auth_oauthlib.flow import InstalledAppFlow
- from googleapiclient.discovery import build
- from googleapiclient.errors import HttpError
- from oauth2client.service_account import ServiceAccountCredentials
- import pandas as pd
- import datetime as dt
- import pymysql
- pymysql.install_as_MySQLdb()
- import gspread
- import requests
- from oauth2client.service_account import ServiceAccountCredentials
- # SETTINGS, FOR DEBUG USE ONLY -- SET TO 0 IF UNUSED, ANY OTHER VALUE WILL TRIGGER THE FUNCTION
- makepres = 0 # generates presentation files, files will be under service/報告 folder
- notifygroup = 0 # notify to LINE group
- # If modifying these scopes, delete the file token.json.
- GOOGLE_APPLICATION_CREDENTIALS="choozmomain-6c1dffe1ee7e.json"
- SCOPES = ['https://www.googleapis.com/auth/drive']
- creds = None
- # The file token.json stores the user's access and refresh tokens, and is
- # created automatically when the authorization flow completes for the first
- # time.
- if os.path.exists('token.json'):
- creds = Credentials.from_authorized_user_file('token.json', SCOPES)
- # If there are no (valid) credentials available, let the user log in.
- if not creds or not creds.valid:
- if creds and creds.expired and creds.refresh_token:
- creds.refresh(Request())
- else:
- flow = InstalledAppFlow.from_client_secrets_file(
- 'credentials.json', SCOPES)
- creds = flow.run_local_server(port=0)
- # Save the credentials for the next run
- with open('token.json', 'w') as token:
- token.write(creds.to_json())
- gc = gspread.authorize(creds)
- sheet = gc.open_by_key('1zpE8NpF7wzGppLlx95V785rnQotapzP-8fZSFOP8Ouk')
- sheet_instance = sheet.get_worksheet(0)
- records_data = sheet_instance.get_all_records()
- clients = []
- for rd in records_data:
- clients.append([rd["client"], rd["alias"].split(','), rd['sheetsid'], rd['discontinued']])
- print(clients) # from left to right: client name, client alias, sheets ID, discontinued flag
- # The ID of the base presentation.
- PRESENTATION_ID = '1dLmTtHF0ZmrXkEDLXOcXaH7NgL6qYZtELVHMGT1Qe7E'
- def notify_group(msg):
- gid='WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD'
- headers = {"Authorization": "Bearer " + gid,"Content-Type": "application/x-www-form-urlencoded"}
- r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params={"message": msg})
- def copy_presentation(presentation_id, copy_title):
- """
- Creates the copy Presentation the user has access to.
- Load pre-authorized user credentials from the environment.
- TODO(developer) - See https://developers.google.com/identity
- for guides on implementing OAuth2 for the application.
- """
- #creds, _ = google.auth.default()
- # pylint: disable=maybe-no-member
- try:
- drive_service = build('drive', 'v3', credentials=creds)
- body = {
- 'name': copy_title
- }
- drive_response = drive_service.files().copy(
- fileId=presentation_id, body=body).execute()
- presentation_copy_id = drive_response.get('id')
- except HttpError as error:
- print(f"An error occurred: {error}")
- print("Presentations not copied")
- return error
- return presentation_copy_id
- def totaling(data):
- d = list(zip(*data))
- res = []
- for i in d:
- try:
- total=0
- for j in i:
- total = total + float(j)
- res.append(total)
- except:
- res.append("NA") # we dont need all the fields anyway, some fields are in text
- print(res)
- return res
- def process_worksheet(ws, client, n):
- data = []
- r = ws.get_all_values()
- r1 = r[12]
- r2 = totaling(list(r[13:]))
- data.append(r1)
- data.append(r2)
- f = pd.DataFrame(data)
- f.columns = f.iloc[0]
- f = f[1:]
- f.to_excel(client + str(n) + " f.xlsx")
- print(f)
- return f
- def getValue(before, after):
- res = after-before
- if res > 0:
- value = "{:,.2f}".format(res)
- return "↑ " + (value.rstrip('0').rstrip('.') if '.' in value else value)
- elif res == 0:
- return "→ 0"
- else:
- value = "{:,.2f}".format(abs(res))
- return "↓ " + (value.rstrip('0').rstrip('.') if '.' in value else value)
- def checkFloat(x):
- print(x)
- if x % 1 == 0.99 or x % 1 == 0.01:
- return round(x)
- return x
- def gather(client, source):
- data = {}
-
- #ads data here
-
- fields1 = ["Clicks (Compare to)", "Impr. (Compare to)", "Avg. CPC (Compare to)", "Avg. CPM (Compare to)", "Conversions (Compare to)", "Cost (Compare to)", "Clicks", "Impr.", "Avg. CPC", "Avg. CPM", "Conversions", "Cost", ]
- fields2 = ["點擊 (比較對象)", "曝光 (比較對象)", "平均單次點擊出價 (比較對象)", "平均單次點擊出價 (比較對象)", "所有轉換 (比較對象)", "費用 (比較對象)","點擊", "曝光", "平均單次點擊出價", "平均單次點擊出價", "所有轉換", "費用"]
-
- credentials = ServiceAccountCredentials.from_json_keyfile_name(GOOGLE_APPLICATION_CREDENTIALS, SCOPES)
- gc = gspread.authorize(credentials)
- sh = gc.open_by_key(source)
- msg = ""
- file = process_worksheet(sh.worksheet(client + ' 前一週D'), client, 1)
- file2 = process_worksheet(sh.worksheet(client + ' 前一週S'), client, 2)
- # quick testing to determine which field names to use -- in English (fields1) or Chinese (fields2)
- try:
- print(file[fields1[0]].values[0])
- fields = fields1
- except:
- fields = fields2
-
- for f in fields: # if NA then it is assumed to be 0 -- we still need to calculate the delta
- if file[f].values[0] == "NA":
- val1 = 0.00
- file[f].values[0] = 0.00
- else:
- val1 = file[f].values[0]
- if file2[f].values[0] == "NA":
- val2 = 0.00
- file2[f].values[0] = 0.00
- else:
- val2 = file2[f].values[0]
- msg += f + ": " + str(val1) + " / " + str(val2) + "\n"
-
- # well, you cant have decimals in clicks or impressions or conversions
- file[fields[0]].values[0] = checkFloat(file[fields[0]].values[0])
- file[fields[6]].values[0] = checkFloat(file[fields[6]].values[0])
- file2[fields[0]].values[0] = checkFloat(file2[fields[0]].values[0])
- file2[fields[6]].values[0] = checkFloat(file2[fields[6]].values[0])
- file[fields[1]].values[0] = checkFloat(file[fields[1]].values[0])
- file[fields[7]].values[0] = checkFloat(file[fields[7]].values[0])
- file2[fields[1]].values[0] = checkFloat(file2[fields[1]].values[0])
- file2[fields[7]].values[0] = checkFloat(file2[fields[7]].values[0])
- file[fields[4]].values[0] = checkFloat(file[fields[4]].values[0])
- file[fields[10]].values[0] = checkFloat(file[fields[10]].values[0])
- file2[fields[4]].values[0] = checkFloat(file2[fields[4]].values[0])
- file2[fields[10]].values[0] = checkFloat(file2[fields[10]].values[0])
- data["{{v111}}"] = file[fields[0]].values[0]
- data["{{v211}}"] = file[fields[1]].values[0]
- data["{{v311}}"] = file[fields[2]].values[0]
- data["{{v411}}"] = file[fields[3]].values[0]
- data["{{v511}}"] = file[fields[4]].values[0]
- data["{{v611}}"] = file[fields[5]].values[0]
- data["{{v121}}"] = file[fields[6]].values[0]
- data["{{v221}}"] = file[fields[7]].values[0]
- data["{{v321}}"] = file[fields[8]].values[0]
- data["{{v421}}"] = file[fields[9]].values[0]
- data["{{v521}}"] = file[fields[10]].values[0]
- data["{{v621}}"] = file[fields[11]].values[0]
- data["{{v112}}"] = file2[fields[0]].values[0]
- data["{{v212}}"] = file2[fields[1]].values[0]
- data["{{v312}}"] = file2[fields[2]].values[0]
- data["{{v412}}"] = file2[fields[3]].values[0]
- data["{{v512}}"] = file2[fields[4]].values[0]
- data["{{v612}}"] = file2[fields[5]].values[0]
- data["{{v122}}"] = file2[fields[6]].values[0]
- data["{{v222}}"] = file2[fields[7]].values[0]
- data["{{v322}}"] = file2[fields[8]].values[0]
- data["{{v422}}"] = file2[fields[9]].values[0]
- data["{{v522}}"] = file2[fields[10]].values[0]
- data["{{v622}}"] = file2[fields[11]].values[0]
-
- data["{{v113}}"] = float(file[fields[0]].values[0]) + float(file2[fields[0]].values[0])
- data["{{v213}}"] = float(file[fields[1]].values[0]) + float(file2[fields[1]].values[0])
- data["{{v313}}"] = float(file[fields[2]].values[0]) + float(file2[fields[2]].values[0])
- data["{{v413}}"] = float(file[fields[3]].values[0]) + float(file2[fields[3]].values[0])
- data["{{v513}}"] = float(file[fields[4]].values[0]) + float(file2[fields[4]].values[0])
- data["{{v613}}"] = float(file[fields[5]].values[0]) + float(file2[fields[5]].values[0])
- data["{{v123}}"] = float(file[fields[6]].values[0]) + float(file2[fields[6]].values[0])
- data["{{v223}}"] = float(file[fields[7]].values[0]) + float(file2[fields[7]].values[0])
- data["{{v323}}"] = float(file[fields[8]].values[0]) + float(file2[fields[8]].values[0])
- data["{{v423}}"] = float(file[fields[9]].values[0]) + float(file2[fields[9]].values[0])
- data["{{v523}}"] = float(file[fields[10]].values[0]) + float(file2[fields[10]].values[0])
- data["{{v623}}"] = float(file[fields[11]].values[0]) + float(file2[fields[11]].values[0])
- data["{{1d1}}"] = getValue(file[fields[0]].values[0],file[fields[6]].values[0])
- data["{{2d1}}"] = getValue(file[fields[1]].values[0],file[fields[7]].values[0])
- data["{{3d1}}"] = getValue(file[fields[2]].values[0],file[fields[8]].values[0])
- data["{{4d1}}"] = getValue(file[fields[3]].values[0],file[fields[9]].values[0])
- data["{{5d1}}"] = getValue(file[fields[4]].values[0],file[fields[10]].values[0])
-
- data["{{1d2}}"] = getValue(file2[fields[0]].values[0],file2[fields[6]].values[0])
- data["{{2d2}}"] = getValue(file2[fields[1]].values[0],file2[fields[7]].values[0])
- data["{{3d2}}"] = getValue(file2[fields[2]].values[0],file2[fields[8]].values[0])
- data["{{4d2}}"] = getValue(file2[fields[3]].values[0],file2[fields[9]].values[0])
- data["{{5d2}}"] = getValue(file2[fields[4]].values[0],file2[fields[10]].values[0])
-
- data["{{1d3}}"] = getValue(file[fields[0]].values[0] + file2[fields[0]].values[0],file[fields[6]].values[0] + file2[fields[6]].values[0])
- data["{{2d3}}"] = getValue(file[fields[1]].values[0] + file2[fields[1]].values[0],file[fields[7]].values[0] + file2[fields[7]].values[0])
- data["{{3d3}}"] = getValue(file[fields[2]].values[0] + file2[fields[2]].values[0],file[fields[8]].values[0] + file2[fields[8]].values[0])
- data["{{4d3}}"] = getValue(file[fields[3]].values[0] + file2[fields[3]].values[0],file[fields[9]].values[0] + file2[fields[9]].values[0])
- data["{{5d3}}"] = getValue(file[fields[4]].values[0] + file2[fields[4]].values[0],file[fields[10]].values[0] + file2[fields[10]].values[0])
-
- for i in data:
- try:
- value = "{:,.2f}".format(data[i])
- data[i] = value.rstrip('0').rstrip('.') if '.' in value else value
- except:
- pass
- return data
- def check(client, data):
- if client == data[0]:
- return True
- for alias in data[1]:
- if client.upper() == alias:
- return True
- return False
- def getClientData(): # currently unused
- client = input("請輸入客戶名稱: ")
- for c in clients:
- if check(client, c):
- return c[0], c[2]
-
- source = input('請輸入Google Ads 資料來源的Google Sheets ID (應有 "[客戶名稱] 前一周" 的活頁簿 : ')
- return client, source
- def process(c, service):
- client = c[0]
- DATA_ID = c[2]
- print (client, DATA_ID)
- # GATHER DATA HERE
- data = gather(client, DATA_ID)
- print(data)
- new_pres = "0000000000"
- if makepres != 0:
- new_pres = copy_presentation(PRESENTATION_ID, client + "週報" + str(dt.date.today().strftime("%Y%m%d")))
- # Call the Slides API
- presentation = service.presentations().get(
- presentationId=new_pres).execute()
- slides = presentation.get('slides')
- print('The presentation contains {} slides:'.format(len(slides)))
- for i, slide in enumerate(slides):
- content = slide.get('pageElements')
- print('- Slide #{} contains {} elements.'.format(
- i + 1, len(content)))
- print(content[0]['objectId'])
-
- # ALL THE STUFF YOU WANT TO DO GOES HERE
- reqs = [
- {'replaceAllText': {'containsText': {'text': '{{client}}'}, 'replaceText': client}},
- {'replaceAllText': {'containsText': {'text': '{{reportdate}}'}, 'replaceText': str(dt.date.today().strftime("%Y/%m/%d"))}},
- {'replaceAllText': {'containsText': {'text': '{{startdate}}'},
- 'replaceText': str(((dt.date.today()) - dt.timedelta(days=7)).strftime("%m/%d"))}},
- {'replaceAllText': {'containsText': {'text': '{{enddate}}'},
- 'replaceText': str(((dt.date.today()) - dt.timedelta(days=1)).strftime("%m/%d"))}},
-
- {'replaceAllText': {'containsText': {'text': '{{v111}}'}, 'replaceText': data["{{v111}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v211}}'}, 'replaceText': data["{{v211}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v311}}'}, 'replaceText': data["{{v311}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v411}}'}, 'replaceText': data["{{v411}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v511}}'}, 'replaceText': data["{{v511}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v611}}'}, 'replaceText': data["{{v611}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v121}}'}, 'replaceText': data["{{v121}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v221}}'}, 'replaceText': data["{{v221}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v321}}'}, 'replaceText': data["{{v321}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v421}}'}, 'replaceText': data["{{v421}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v521}}'}, 'replaceText': data["{{v521}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v621}}'}, 'replaceText': data["{{v621}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v112}}'}, 'replaceText': data["{{v112}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v212}}'}, 'replaceText': data["{{v212}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v312}}'}, 'replaceText': data["{{v312}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v412}}'}, 'replaceText': data["{{v412}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v512}}'}, 'replaceText': data["{{v512}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v612}}'}, 'replaceText': data["{{v612}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v122}}'}, 'replaceText': data["{{v122}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v222}}'}, 'replaceText': data["{{v222}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v322}}'}, 'replaceText': data["{{v322}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v422}}'}, 'replaceText': data["{{v422}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v522}}'}, 'replaceText': data["{{v522}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v622}}'}, 'replaceText': data["{{v622}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v113}}'}, 'replaceText': data["{{v113}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v213}}'}, 'replaceText': data["{{v213}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v313}}'}, 'replaceText': data["{{v313}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v413}}'}, 'replaceText': data["{{v413}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v513}}'}, 'replaceText': data["{{v513}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v613}}'}, 'replaceText': data["{{v613}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v123}}'}, 'replaceText': data["{{v123}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v223}}'}, 'replaceText': data["{{v223}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v323}}'}, 'replaceText': data["{{v323}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v423}}'}, 'replaceText': data["{{v423}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v523}}'}, 'replaceText': data["{{v523}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{v623}}'}, 'replaceText': data["{{v623}}"]}},
-
- {'replaceAllText': {'containsText': {'text': '{{1d1}}'}, 'replaceText': data["{{1d1}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{2d1}}'}, 'replaceText': data["{{2d1}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{3d1}}'}, 'replaceText': data["{{3d1}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{4d1}}'}, 'replaceText': data["{{4d1}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{5d1}}'}, 'replaceText': data["{{5d1}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{1d2}}'}, 'replaceText': data["{{1d2}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{2d2}}'}, 'replaceText': data["{{2d2}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{3d2}}'}, 'replaceText': data["{{3d2}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{4d2}}'}, 'replaceText': data["{{4d2}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{5d2}}'}, 'replaceText': data["{{5d2}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{1d3}}'}, 'replaceText': data["{{1d3}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{2d3}}'}, 'replaceText': data["{{2d3}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{3d3}}'}, 'replaceText': data["{{3d3}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{4d3}}'}, 'replaceText': data["{{4d3}}"]}},
- {'replaceAllText': {'containsText': {'text': '{{5d3}}'}, 'replaceText': data["{{5d3}}"]}},
- ]
- service.presentations().batchUpdate(body={'requests':reqs}, presentationId=new_pres, fields='').execute()
- return "https://docs.google.com/presentation/d/" + new_pres # returns link of presentation
- def main():
- msg = "以下是客戶的Ads週報,相關負責人員請完成填寫報告內容,謝謝。\n\n"
- try:
- service = build('slides', 'v1', credentials=creds)
- #client, DATA_ID = getClientData()
- for c in clients:
- if c[3]!=1: # if client is not discontinued
- link = process(c, service)
- msg += c[0] + ": " + link + "\n"
- print(msg)
- if notifygroup != 0:
- notify_group(msg)
- except HttpError as err:
- print(err)
- if __name__ == '__main__':
- main()
|