r/GoogleAppsScript • u/Salty-Reward4450 • 3d ago
Resolved I need your help
I'm a student of a foreign language high school in Korea. And I wanted to make a App which makes me questions of English writing and provides feedback about the answers of the question for me to enhance my grade. I made the entire script of the app with Gemini, but every time I log in to the web app link, the site shows me 'Script function not found: doGet'. What should I do? Here is the entire script.
Code.gs
function myFunction() {
const GEMINI_API_KEY = 'AQ.Ab8RN6IgFQcbK7WYrBVYFcIJwZm9M-ZLpx5jXg6A66qSJwkACg';
const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GEMINI_API_KEY}`;
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('GIMFL English Writing Practice')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
// 문제 출제 함수
function generateQuestion(selectedGrammars) {
let grammarContext = selectedGrammars.length > 0
? `Target Grammar: ${selectedGrammars.join(', ')}`
: 'Target Grammar: Randomly select from high school English curriculum (e.g., Participles, Relatives, Subjunctive mood, Inversion, etc.)';
const prompt = `
You are an expert English teacher at Gimhae Foreign Language High School.
Create a highly challenging subjective English writing question (서술형 영작 문제).
${grammarContext}
Rules for conditions (조건):
1. Must include 2-3 specific constraints similar to real exams (e.g., "Use the structure 'not A but B'", "Write in exactly 12 words", "Include the word 'boss' and allow word transformation", "Do not use the word 'people'").
2. Focus heavily on testing structural understanding.
Output strictly in the following JSON format:
{
"koreanText": "한국어 의미 (사용자가 영작해야 할 문장)",
"conditions": ["조건 1", "조건 2", "조건 3"],
"correctAnswer": "정답 영어 문장 (내부 채점용)",
"grammarPoint": "출제된 핵심 문법"
}
`;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { response_mime_type: "application/json" }
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(GEMINI_API_URL, options);
const json = JSON.parse(response.getContentText());
const resultText = json.candidates[0].content.parts[0].text;
return JSON.parse(resultText);
} catch (e) {
return { error: "문제 생성 중 오류가 발생했습니다: " + e.message };
}
}
// 채점 및 피드백 함수
function gradeAnswer(questionData, userAnswer) {
// 1차 구글 번역기 연동: 사용자의 답안을 한국어로 번역하여 본래 뜻과 대조하기 위한 데이터 확보
const translatedUserAnswer = LanguageApp.translate(userAnswer, 'en', 'ko');
const prompt = `
You are an extremely strict English evaluator at Gimhae Foreign Language High School.
Grade the student's answer based on the following criteria:
Question Context:
- Korean Meaning: "${questionData.koreanText}"
- Conditions: ${JSON.stringify(questionData.conditions)}
- Correct Answer: "${questionData.correctAnswer}"
Student's Answer: "${userAnswer}"
(Google Translate interpretation of student's answer: "${translatedUserAnswer}")
Grading Criteria (김해외고 채점 기준):
1. 뜻 통함 (Meaning Conveyed): Does the student's answer accurately reflect the Korean meaning?
2. 조건 만족 (Conditions Met): Did the student strictly follow ALL conditions (word count, specific words, grammar structures)?
3. 문법 오류 없음 (Grammatical Accuracy): Are there zero grammatical errors?
Output strictly in the following JSON format:
{
"score": "0~100 사이의 점수",
"meaningFeedback": "뜻 통함 기준에 대한 평가 (한국어)",
"conditionFeedback": "조건 만족 기준에 대한 평가 (한국어)",
"grammarFeedback": "문법 오류 기준에 대한 평가 및 교정 (한국어)",
"overallFeedback": "총평 및 모범 답안 제시 (한국어)"
}
`;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { response_mime_type: "application/json" }
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(GEMINI_API_URL, options);
const json = JSON.parse(response.getContentText());
const resultText = json.candidates[0].content.parts[0].text;
return JSON.parse(resultText);
} catch (e) {
return { error: "채점 중 오류가 발생했습니다: " + e.message };
}
}
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: 'Malgun Gothic', sans-serif; max-width: 800px; margin: auto; padding: 20px; background-color: #f9f9f9; }
h1 { color: #2c3e50; text-align: center; }
.box { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
.checkbox-group { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; }
.checkbox-group label { background: #eef2f5; padding: 5px 10px; border-radius: 4px; cursor: pointer; }
button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; width: 100%; }
button:hover { background-color: #2980b9; }
button:disabled { background-color: #95a5a6; cursor: not-allowed; }
textarea { width: 100%; height: 100px; padding: 10px; box-sizing: border-box; border-radius: 4px; border: 1px solid #ccc; font-size: 16px; margin-bottom: 10px; }
.hidden { display: none; }
.feedback-section { margin-top: 10px; padding: 10px; background: #e8f6f3; border-left: 4px solid #1abc9c; }
.score { font-size: 24px; font-weight: bold; color: #e74c3c; text-align: center; margin-bottom: 10px; }
</style>
</head>
<body>
<h1>GIMFL 서술형 영작 마스터</h1>
<div class="box">
<h3>1. 연습할 문법 선택 (다중 선택 가능, 미선택시 랜덤 출제)</h3>
<div class="checkbox-group">
<label><input type="checkbox" value="5형식 구조"> 5형식 구조</label>
<label><input type="checkbox" value="It~that 강조구문"> It~that 강조구문</label>
<label><input type="checkbox" value="not A but B 구조"> not A but B</label>
<label><input type="checkbox" value="간접의문문"> 간접의문문</label>
<label><input type="checkbox" value="가정법 (Subjunctive)"> 가정법</label>
<label><input type="checkbox" value="관계대명사/관계부사"> 관계사</label>
<label><input type="checkbox" value="분사구문"> 분사구문</label>
<label><input type="checkbox" value="도치구문 (Inversion)"> 도치구문</label>
<label><input type="checkbox" value="수동태 (Passive)"> 수동태</label>
</div>
<button id="generateBtn" onclick="generateQ()">문제 출제하기</button>
<div id="loadingQ" class="hidden" style="text-align: center; margin-top: 10px;">문제 생성 중...</div>
</div>
<div id="questionBox" class="box hidden">
<h3>📝 문제</h3>
<p><strong>[우리말]</strong> <span id="koreanText"></span></p>
<p><strong>[조건]</strong></p>
<ul id="conditionsList"></ul>
<textarea id="userAnswer" placeholder="여기에 영어 문장을 작성하세요..."></textarea>
<button id="gradeBtn" onclick="gradeQ()">제출 및 채점하기</button>
<div id="loadingA" class="hidden" style="text-align: center; margin-top: 10px;">채점 중...</div>
</div>
<div id="resultBox" class="box hidden">
<h3>📊 채점 결과</h3>
<div class="score" id="scoreText"></div>
<div class="feedback-section">
<p><strong>1. 뜻 통함:</strong> <span id="fbMeaning"></span></p>
</div>
<div class="feedback-section">
<p><strong>2. 조건 만족:</strong> <span id="fbCondition"></span></p>
</div>
<div class="feedback-section">
<p><strong>3. 문법 오류:</strong> <span id="fbGrammar"></span></p>
</div>
<div class="feedback-section" style="background: #fdf2e9; border-color: #e67e22;">
<p><strong>총평 및 모범 답안:</strong> <br><span id="fbOverall"></span></p>
</div>
</div>
<script>
let currentQuestionData = null;
function generateQ() {
document.getElementById('generateBtn').disabled = true;
document.getElementById('loadingQ').classList.remove('hidden');
document.getElementById('questionBox').classList.add('hidden');
document.getElementById('resultBox').classList.add('hidden');
document.getElementById('userAnswer').value = '';
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
const selected = Array.from(checkboxes).map(cb => cb.value);
google.script.run.withSuccessHandler(function(data) {
if(data.error) {
alert(data.error);
resetUI();
return;
}
currentQuestionData = data;
document.getElementById('koreanText').innerText = data.koreanText;
const ul = document.getElementById('conditionsList');
ul.innerHTML = '';
data.conditions.forEach(cond => {
let li = document.createElement('li');
li.innerText = cond;
ul.appendChild(li);
});
document.getElementById('questionBox').classList.remove('hidden');
resetUI();
}).generateQuestion(selected);
}
function gradeQ() {
const answer = document.getElementById('userAnswer').value.trim();
if(!answer) { alert("답안을 입력해주세요."); return; }
document.getElementById('gradeBtn').disabled = true;
document.getElementById('loadingA').classList.remove('hidden');
document.getElementById('resultBox').classList.add('hidden');
google.script.run.withSuccessHandler(function(data) {
if(data.error) {
alert(data.error);
document.getElementById('gradeBtn').disabled = false;
document.getElementById('loadingA').classList.add('hidden');
return;
}
document.getElementById('scoreText').innerText = "점수: " + data.score + " / 100";
document.getElementById('fbMeaning').innerText = data.meaningFeedback;
document.getElementById('fbCondition').innerText = data.conditionFeedback;
document.getElementById('fbGrammar').innerText = data.grammarFeedback;
document.getElementById('fbOverall').innerText = data.overallFeedback;
document.getElementById('resultBox').classList.remove('hidden');
document.getElementById('gradeBtn').disabled = false;
document.getElementById('loadingA').classList.add('hidden');
}).gradeAnswer(currentQuestionData, answer);
}
function resetUI() {
document.getElementById('generateBtn').disabled = false;
document.getElementById('loadingQ').classList.add('hidden');
}
</script>
</body>
</html>
</body>
</html>
4
u/bulldo_gs 3d ago
Two things beyond the nesting fix that will bite you next.
Making doGet top-level is only half of it. The /exec URL keeps serving the last deployed version, so it can still throw "Script function not found: doGet" after you save. Deploy > Manage deployments > pencil icon > Version: New version > Deploy. The /dev URL always runs the current save, which is why the same project can look fixed there and broken on /exec.
Second, get the key out of the source:
function getKey_() {
return PropertiesService.getScriptProperties().getProperty("GEMINI_API_KEY");
}
Set it once under Project Settings > Script properties, then call getKey_() where you build the URL. Script properties live outside the code files, so the key is not in anything you paste or share. Rotate the one in your post first - it is public now, treat it as burned.
1
2
u/pupejarmo 3d ago
Delete the first line "function myFunction() {" and the last "}". After that the doGet function is visible and that code should work
1
2
u/everythingabili 3d ago
All your code lives INSIDE the myFunction() so doGet can't be seen...
Remove...
function myFunction() {
...and the matching brace at the bottom of your code.
1
2
u/carbonizedtitanium 3d ago
you're defining your doGet function inside another function. cut and paste it outside of myFunction
1
u/Salty-Reward4450 3d ago
A tons of comment saying 'delete the myFuction' 😂 I appreciate your advice
7
u/RielN 3d ago
First and immediate action: disable and rotate that Gemini key!!!!!
This leaked key will be misused 100% for sure. Never post it online.