From 8c4fb14bcf56058857293b1746efa8e3c364a3e4 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sun, 26 Feb 2023 20:29:34 +0700 Subject: [PATCH] sudah bisa ambil data di spreedsheet dan update ke gform --- Scripts/Python/Library/gquiz.py | 199 +++++++++++++++++++++++++++++++ Scripts/Python/quiz_generator.py | 56 +++++++++ Scripts/Python/test_gquiz.py | 29 +++++ myedu.ods | Bin 10455 -> 17344 bytes 4 files changed, 284 insertions(+) create mode 100644 Scripts/Python/Library/gquiz.py create mode 100644 Scripts/Python/quiz_generator.py create mode 100644 Scripts/Python/test_gquiz.py diff --git a/Scripts/Python/Library/gquiz.py b/Scripts/Python/Library/gquiz.py new file mode 100644 index 0000000..3d5af35 --- /dev/null +++ b/Scripts/Python/Library/gquiz.py @@ -0,0 +1,199 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import os.path +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 pprint import pprint +import requests +from urllib.parse import urlparse + +class gquiz: + def __init__(self): + ''' + Args : + templateId : get the id from link GoogleForm + ''' + self.image_temp_service_url = "https://tmpfiles.org/api/v1/upload" + self.submition = {"requests":[]} + self.form_service = None + self.drive_service = None + self.main_form = None + + self.submition["requests"].append({ + "updateSettings": { + "updateMask": "*" , + "settings": { + "quizSettings": { + "isQuiz": True + } + } + } + }) + + def generateService(self): + ''' Start Tokenizing + here is the way to get token + link : https://developers.google.com/docs/api/quickstart/python + ''' + SCOPES = ["https://www.googleapis.com/auth/forms.body", + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.file", + "https://www.googleapis.com/auth/drive.appdata"] + DISCOVERY_DOC = "https://forms.googleapis.com/$discovery/rest?version=v1" + + 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( + './secret/client_secrets.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()) + + try: + service = build('docs', 'v1', credentials=creds) + self.form_service, self.drive_service = \ + build('forms', 'v1', credentials=creds, discoveryServiceUrl=DISCOVERY_DOC, static_discovery=False),\ + build('drive', 'v3', credentials=creds) + + except HttpError as err: + print(err) + ''' End Tokenizing + ''' + + def copyFile(self,origin_file_id, copy_title): + """Copy an existing file. + Args: + service: Drive API service instance. + origin_file_id: ID of the origin file to copy. + copy_title: Title of the copy. + + Returns: + The copied file if successful, None otherwise. + """ + + try: + if((self.drive_service == None) or (self.form_service == None)): + raise Exception('please generate service first') + + newFormId = self.drive_service.files().copy(\ + fileId=origin_file_id, body={"name":copy_title})\ + .execute() + print(newFormId) + self.main_form = self.form_service.forms().get(formId=newFormId["id"]).execute() + + except HttpError as error: + print('An error occurred: %s' % error) + return None + + def createOption(self, value, image=None): + ''' + return {"value" : "A. option 1"} + return {"value": "Option", + "image": { + "sourceUri": "", + "properties": { + "alignment": "LEFT" + } + }} + ''' + opt = {"value" : value} + if(image != None): + print("print with image, uploading... ") + req = requests.post(self.image_temp_service_url,files={"file": open(image,'rb')}) + if(req.json()['status'] == 'error'): + raise Exception("upload failed : {}".format(req.json())) + print("success") + u = urlparse(req.json()['data']['url']) + opt.update({"image" : { + "sourceUri": u._replace(path="/dl"+u.path).geturl(), + "properties": { + "alignment": "LEFT" + } + }}) + return opt + + def createQuestion(self, title, description, options, indexAnswer, itemImage=None): + ''' + Args: + "title" : String + "desc" : String + "indexAnswer" : Integer, index for "options" + "options" : + [{"value" : "A. option 1"}, + {"value" : "B. option 2"}, + {"value" : "C. option 3"}, + {"value" : "D. option 4"}, + {"value" : "E. option 5"},] + ''' + item = { + "title" : title, + "description" : description, + "questionItem" : { + "question" : { + "grading" : { + "pointValue": 1, + "correctAnswers": { + "answers" : [ {"value": options[indexAnswer-1]["value"]} ] + } + }, + "choiceQuestion" : { + "type" : "RADIO", + "options" : options + } + } + } + } + if (itemImage != None): + print("uploading image for quesiton : ") + req = requests.post(self.image_temp_service_url,files={"file": open(itemImage,'rb')}) + if(req.json()['status'] == 'error'): + raise Exception("upload failed : {}".format(req.json())) + print("succes") + u = urlparse(req.json()['data']['url']) + item['questionItem'].update(\ + {"image": { + "sourceUri": u._replace(path="/dl"+u.path).geturl(), + "properties": { + "alignment": "CENTER" + } + } + }) + return item + + def submitQuestion(self, index, item): + """ Submit item to submition + Args: + index : location item in form + item : object item + """ + self.submition['requests'].append({ + "createItem" : { + "location": {"index": index}, + "item": item + } + }) + pprint(self.submition) + + + def update(self): + # Adds the question to the form + question_setting = self.form_service.forms().batchUpdate(formId=self.main_form["formId"], body=self.submition).execute() + print(question_setting) + + # Prints the result to show the question has been added + get_result = self.form_service.forms().get(formId=self.main_form["formId"]).execute() + print(get_result) diff --git a/Scripts/Python/quiz_generator.py b/Scripts/Python/quiz_generator.py new file mode 100644 index 0000000..268a5c9 --- /dev/null +++ b/Scripts/Python/quiz_generator.py @@ -0,0 +1,56 @@ +# coding: utf-8 +from __future__ import unicode_literals +from scriptforge import CreateScriptService + + +def generateTemplate(): + doc = CreateScriptService("Calc") + basic = CreateScriptService("Basic") + doc.SetArray(doc.CurrentSelection, \ + (("","", \ + "", "", "", \ + "", "", "", \ + "", "", "", \ + "", "", "", \ + "", "", "", ),)) + +def updateQuestion(): + import sys, os + from unohelper import fileUrlToSystemPath + if not 'gquiz' in sys.modules: + doc = XSCRIPTCONTEXT.getDocument() + url = fileUrlToSystemPath('{}/{}'.format(doc.URL,'Scripts/python/Library')) + sys.path.insert(0, url) + from gquiz import gquiz + import requests + + + doc = CreateScriptService("Calc") + item = doc.getValue(doc.Offset(doc.CurrentSelection,0,0,1,17)) + + q = gquiz() + + q.generateService() + + q.copyFile("16-rh3W-NwYzdKVBZJmi574sTWe_rMIdE-FQSw_33qXI", "Demo Soal") + + opt = [] + + theAnswer = -1 + c = 1 + for o in range(2,17,3): + if (item[o] == 1): + theAnswer = c + c = c+1 + opt.append(q.createOption(item[o+2],os.getcwd()+item[o+1])) + + if (theAnswer == -1): + raise Exception("Chose the correct answer") + + qq = q.createQuestion(title = "Soal No 1",\ + description = item[0],\ + indexAnswer = theAnswer, options = opt, itemImage=os.getcwd()+item[1]) + q.submitQuestion(0,qq) + q.update() + +g_exportedScripts = (generateTemplate, updateQuestion, ) diff --git a/Scripts/Python/test_gquiz.py b/Scripts/Python/test_gquiz.py new file mode 100644 index 0000000..c36fdc9 --- /dev/null +++ b/Scripts/Python/test_gquiz.py @@ -0,0 +1,29 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import sys, requests +from unohelper import fileUrlToSystemPath + +def gquiz_test(): + if not 'gquiz' in sys.modules: + doc = XSCRIPTCONTEXT.getDocument() + url = fileUrlToSystemPath('{}/{}'.format(doc.URL,'Scripts/python/Library')) + sys.path.insert(0, url) + from gquiz import gquiz + + q = gquiz() + + q.generateService() + + q.copyFile("16-rh3W-NwYzdKVBZJmi574sTWe_rMIdE-FQSw_33qXI", "Soal Masuk Penjara") + opt = [ q.createOption("A.","./asset/option1.png"), + q.createOption("B.","./asset/option2.png"), + q.createOption("C.","./asset/option3.png"), + q.createOption("D.","./asset/option4.png"), + q.createOption("E.","./asset/option5.png") ] + + qq = q.createQuestion(title = "Soal No 1",\ + description = "Dari gambar dibawah ini, ada bagian gambar yang hilang. Dari pilihan dibawah, manakah gambar yang benar?",\ + indexAnswer = 4, options = opt, itemImage='./asset/test_image.png') + q.submitQuestion(0,qq) + q.update() diff --git a/myedu.ods b/myedu.ods index c6cda0fe1171d59846b6e564f7d38ab4f2347ad1..4e8a941e476d6836af78fda7e12f939224f4d857 100644 GIT binary patch delta 11006 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 ln3?{v`FB|UipqWBq6Yvd0AyPyQyM|6 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 Z|8MrodSbk-0B|0Eoe%*)&xN0({{yYt1c3km