Skip to content

Commit

Permalink
Merge pull request #135 from scombz-utilities/feature/google-classroo…
Browse files Browse the repository at this point in the history
…m-api

Google Classroom連携機能
  • Loading branch information
yudai1204 authored Jan 8, 2025
2 parents 55d0b42 + 2b03f8e commit 30aed5a
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 14 deletions.
1 change: 1 addition & 0 deletions .env.local.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CRX_PUBLIC_KEY=""
35 changes: 35 additions & 0 deletions afterBuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
import os


# chrome向けビルド以外のmanifest.jsonから、keyとoauth2とidentityの設定を削除する


def afterBuild():

build_dirs = [
d
for d in os.listdir("build")
if "prod" in d and os.path.isdir(os.path.join("build", d)) and "chrome" not in d
]

for build_dir in build_dirs:
manifest_path = os.path.join("build", build_dir, "manifest.json")
with open(manifest_path, "r") as f:
manifest = json.load(f)
# keyとoauth2とidentityの設定を削除
manifest.pop("key", None)
manifest.pop("oauth2", None)
# permissionsからidentityを削除
if "permissions" in manifest:
manifest["permissions"] = [
p for p in manifest["permissions"] if p != "identity"
]
with open(manifest_path, "w") as f:
json.dump(manifest, f)

print(f"Removed done: {manifest_path}")


if __name__ == "__main__":
afterBuild()
29 changes: 21 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{
"name": "scombz-utilities",
"displayName": "ScombZ Utilities",
"version": "4.3.4",
"version": "4.4.0",
"description": "__MSG_extensionDescription__",
"author": "udai",
"scripts": {
"dev": "plasmo dev",
"dev:firefox": "plasmo dev --target=firefox-mv2",
"build:chrome": "plasmo build",
"build:firefox": "plasmo build --target=firefox-mv2",
"build": "plasmo build && plasmo build --target=firefox-mv2",
"package": "plasmo package && plasmo package --target=firefox-mv2",
"build:chrome": "plasmo build --target=chrome-mv3",
"build:edge": "plasmo build --target=edge-mv3 && npm run afterBuild",
"build:firefox": "plasmo build --target=firefox-mv2 && npm run afterBuild",
"build": "plasmo build --target=chrome-mv3 && plasmo build --target=edge-mv3 && plasmo build --target=firefox-mv2 && npm run afterBuild",
"package": "plasmo package --target=chrome-mv3 && plasmo package --target=edge-mv3 && plasmo package --target=firefox-mv2",
"eslint": "eslint '**/*.ts?(x)' --fix",
"publish": "plasmo build && plasmo build --target=firefox-mv2 && plasmo package && plasmo package --target=firefox-mv2 && zip -r build/sources.zip src assets css locales package.json .env.production tsconfig.json README.md && publish-extension --chrome-zip build/chrome-mv3-prod.zip --firefox-zip build/firefox-mv2-prod.zip --firefox-sources-zip build/sources.zip --edge-zip build/chrome-mv3-prod.zip"
"afterBuild": "python ./afterBuild.py",
"publish": "npm run build && npm run package && zip -r build/sources.zip src assets css locales package.json .env.production tsconfig.json README.md && publish-extension --chrome-zip build/chrome-mv3-prod.zip --firefox-zip build/firefox-mv2-prod.zip --firefox-sources-zip build/sources.zip --edge-zip build/edge-mv3-prod.zip"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
Expand Down Expand Up @@ -71,10 +73,21 @@
],
"permissions": [
"storage",
"browsingData"
"browsingData",
"identity"
],
"host_permissions": [
"http://bus.shibaura-it.ac.jp/"
]
],
"key": "$CRX_PUBLIC_KEY",
"oauth2": {
"client_id": "674854842294-2foqvpq1io2mecbfs0onasqq4t76dht7.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.course-work.readonly",
"https://www.googleapis.com/auth/classroom.student-submissions.me.readonly",
"https://www.googleapis.com/auth/userinfo.email"
]
}
}
}
19 changes: 18 additions & 1 deletion src/background.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { updateBadgeText } from "./backgrounds/badge";
import { getJson } from "./backgrounds/getJson";
import { getClasses, logoutGoogle } from "./backgrounds/google";
import { onInstalled } from "./backgrounds/onInstalled";

export type RuntimeMessage = {
action: "openOption" | "updateBadgeText" | "openNewTabInBackground" | "getJson" | "openNewTab" | "clearCache";
action:
| "openOption"
| "updateBadgeText"
| "openNewTabInBackground"
| "getJson"
| "openNewTab"
| "clearCache"
| "getClasses"
| "logoutGoogle";
url?: string;
};

chrome.runtime.onMessage.addListener((message: RuntimeMessage, _sender, sendResponse) => {
switch (message.action) {
case "getClasses":
if (process.env.PLASMO_BROWSER === "chrome") {
getClasses(sendResponse);
}
break;
case "logoutGoogle":
logoutGoogle();
break;
case "openOption":
chrome.runtime.openOptionsPage();
break;
Expand Down
3 changes: 2 additions & 1 deletion src/backgrounds/badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ export const updateBadgeText = () => {
const surveyList = allSurveyList.filter((task) => notifySurveySubjectsName.includes(task.course));

const originalTasklist = currentData.scombzData.originalTasklist;
const classroomTasklist = currentData.scombzData.classroomTasklist;

const mergedTaskList = [...tasklist, ...surveyList, ...originalTasklist];
const mergedTaskList = [...tasklist, ...surveyList, ...originalTasklist, ...classroomTasklist];

const now = new Date().getTime();

Expand Down
127 changes: 127 additions & 0 deletions src/backgrounds/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import type { Saves } from "~settings";
import { defaultSaves } from "~settings";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getClasses = async (sendResponse?: (response: any) => void) => {
try {
console.log("getClasses Start");

// トークンを取得
const token = await new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: true }, (token) => {
if (chrome.runtime.lastError || !token) {
reject(chrome.runtime.lastError || "トークン取得に失敗しました。");
} else {
resolve(token);
}
});
});

// メールアドレスを取得
const profileResponse = await fetch("https://www.googleapis.com/oauth2/v1/userinfo", {
headers: { Authorization: `Bearer ${token}` },
});
const profile = await profileResponse.json();
console.log("profile", profile);

if (sendResponse) {
sendResponse({ profile });
}

// クラス一覧を取得
const coursesResponse = await fetch("https://classroom.googleapis.com/v1/courses", {
headers: { Authorization: `Bearer ${token}` },
});
const courses = await coursesResponse.json();

// 各クラスの課題を取得
const courseWorkResponses = await Promise.all(
(courses.courses || []).map((course) =>
fetch(`https://classroom.googleapis.com/v1/courses/${course.id}/courseWork`, {
headers: { Authorization: `Bearer ${token}` },
}),
),
);

console.log("wait courseWorkResponses");
const courseWorks = await Promise.all(courseWorkResponses.map((response) => response.json()));

const tasks = [];
const now = new Date();

courseWorks.forEach((courseWork, index) => {
const course = courses.courses[index];
console.log(courseWork);

courseWork.courseWork.forEach(async (work) => {
if (!work.dueDate) return;
const dueDate = new Date(
work.dueDate.year,
work.dueDate.month,
work.dueDate.day,
work.dueTime?.hours ?? 23,
work.dueTime?.minutes ?? 59,
);
if (dueDate < now) return;

tasks.push({
kind: "classroomTask",
course: course.name,
courseId: work.courseId,
courseURL: course.alternateLink,
title: work.title,
link: work.alternateLink,
deadline: `${work.dueDate.year}-${work.dueDate.month}-${work.dueDate.day} ${work.dueTime?.hours ?? 23}:${work.dueTime?.minutes ?? 59}`,
id: work.id,
});
});
});

const submissionResponse = await Promise.all(
tasks.map((task) =>
fetch(`https://classroom.googleapis.com/v1/courses/${task.courseId}/courseWork/${task.id}/studentSubmissions`, {
headers: {
Authorization: `Bearer ${token}`,
},
}),
),
);

const submissionsData = await Promise.all(submissionResponse.map((response) => response.json()));

console.log(submissionsData);

const submissions = submissionsData.map((data) => data.studentSubmissions || []);

const noSubmittedTasks = tasks.map((task, index) => {
return {
isSubmitted: !submissions[index]
.map((submission) => ["CREATED", "RECLAIMED_BY_STUDENT"].includes(submission.state))
.some(Boolean),
...task,
};
});

console.log("noSubmittedTasks", noSubmittedTasks);

chrome.storage.local.get(defaultSaves, (currentData: Saves) => {
currentData.scombzData.classroomTasklist = noSubmittedTasks.filter((task) => !task.isSubmitted);
chrome.storage.local.set(currentData, () => {
console.log("classroomTasklist saved");
});
});
console.log("getClasses End");
} catch (error) {
console.error("課題取得エラー:", error);
sendResponse({ error });
}
};

export const logoutGoogle = () => {
chrome.identity.clearAllCachedAuthTokens(() => {
console.log("removed cache");
});
const url = chrome.identity.getRedirectURL();
console.log("logoutGoogle", url);
chrome.tabs.create({ url: `https://accounts.google.com/logout` });
};
3 changes: 2 additions & 1 deletion src/contents/components/TaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,11 @@ export const TaskList = (props: Props) => {
: allSurveyList.filter((task) => notifySurveySubjectsName.includes(task.course));

const originalTasklist = currentData.scombzData.originalTasklist;
const classroomTasklist = currentData.scombzData.classroomTasklist;

const now = new Date();

const combinedTaskList = [...normalTaskList, ...surveyList, ...originalTasklist]
const combinedTaskList = [...normalTaskList, ...surveyList, ...originalTasklist, ...classroomTasklist]
.map((task) => {
return { ...task, deadlineDate: new Date(task.deadline) };
})
Expand Down
3 changes: 2 additions & 1 deletion src/contents/innerPageTaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ const insertTasklist = async (currentData: Saves) => {
: allSurveyList.filter((task) => notifySurveySubjectsName.includes(task.course));

const originalTasklist = currentData.scombzData.originalTasklist;
const classroomTasklist = currentData.scombzData.classroomTasklist;

const now = new Date();

const tasklist = [...normalTaskList, ...surveyList, ...originalTasklist]
const tasklist = [...normalTaskList, ...surveyList, ...originalTasklist, ...classroomTasklist]
.map((task) => {
return { ...task, deadlineDate: new Date(task.deadline) };
})
Expand Down
4 changes: 4 additions & 0 deletions src/contents/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Task } from "./types/task";
import { getTasksOnTaskPage, getTasksByAjax, fetchSurveys } from "./util/getTaskList";
import { defaultSaves } from "./util/settings";
import type { Saves } from "./util/settings";
import type { RuntimeMessage } from "~/background";
import { FETCH_INTERVAL } from "~/constants";

export const config: PlasmoCSConfig = {
Expand Down Expand Up @@ -42,6 +43,9 @@ export const fetchTasks = async (forceExecute?: boolean) => {
try {
console.log(await getTasksByAjax());
console.log(await fetchSurveys());
if (currentData.settings.googleClassroom.isSignedIn) {
chrome.runtime.sendMessage({ action: "getClasses" } as RuntimeMessage);
}
} catch (e) {
console.error(e);
throw e;
Expand Down
2 changes: 1 addition & 1 deletion src/contents/types/task.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type Task = {
kind: "task" | "originalTask" | "survey";
kind: "task" | "originalTask" | "survey" | "classroomTask";
course: string;
title: string;
link: string;
Expand Down
Loading

0 comments on commit 30aed5a

Please sign in to comment.