diff --git a/.github/workflows/bundle_js.yaml b/.github/workflows/bundle_js.yaml
new file mode 100644
index 0000000..d731eb1
--- /dev/null
+++ b/.github/workflows/bundle_js.yaml
@@ -0,0 +1,41 @@
+name: Bundle JavaScript
+
+on:
+ workflow_dispatch: # Trigger on manual run
+ push:
+ branches:
+ - main # Trigger on push to the 'main' branch
+ pull_request:
+ branches:
+ - main # Trigger on pull request to 'main' branch
+
+jobs:
+ bundle-js:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '14' # Specify the Node.js version
+
+ - name: Install Dependencies
+ run: |
+ npm install terser
+
+ - name: Concatenate and Minify JavaScript
+ run: |
+ # Create a combined JS file from all *.js files in the 'js/src' directory
+ npx terser js/src/*.js -o js/dist/bundle.min.js --compress --mangle
+
+ - name: Commit Minified File
+ run: |
+ # Commit the minified file if it has changed
+ git config --global user.name "GitHub Actions"
+ git config --global user.email "github-actions@github.com"
+ git add js/dist/bundle.min.js
+ git commit -m "Merged and combined JavaScript files to 'dist/bundle.min.js'"
+ git push
diff --git a/README.md b/README.md
index adcc0f2..576bcb2 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,81 @@
-# sdk
-
+
+
+![GitHub Repo stars](https://img.shields.io/github/stars/itu-helper/sdk)
+![GitHub issues](https://img.shields.io/github/issues-raw/itu-helper/sdk?label=Issues&style=flat-square)
+
+# **ITU Helper**
+
+
+
+
+
+
+
+
+_İTÜ'lüler için İTÜ'lülerden_
+
+_ITU Helper_ İstanbul Teknik Üniversitesi öğrencilerine yardım etmek amacıyla ön şart görselleştirme, ders planı oluşturma ve resmi İTÜ sitelerini birleştirme gibi hizmetler sağlayan bir açık kaynaklı websitesidir.
+
+_ITU Helper_'a [_bu adresten_](https://itu-helper.github.io/home/) ulaşabilirsiniz.
+
+
+
+
+
+
+# **itu-helper/sdk**
+
+## **Ne İşe Yarar?**
+
+[itu-helper/data-updater](https://github.com/itu-helper/data-updater) _repo_'suyla toplanan ve [itu-helper/data](https://github.com/itu-helper/data) _repo_'sunda saklanan verilere, kolayca ulaşılmasına olanak sağlar.
+
+> [!NOTE]
+> Verilerin nasıl isimlendirildiğine ve güncelleme sıklığına ulaşmak için, [itu-helper/data-updater](https://github.com/itu-helper/data-updater) _repo_'sununa bakınız.
+
+## **Nasıl Kullanılır?**
+
+### **JavaScript**
+
+`` _tag_'inin en alt kısmına şu satırı ekleyerek _script_'leri importlamanız lazım.
+
+```html
+
+```
+
+JavaScript SDK'sinin detaylı kullanımı için [buraya](js/README.md) bakınız.
+
+### **HTTP Request**
+
+Programlama dilinden bağımsız olarak, verilere _HTTP request_ göndererek de ulaşabilirsiniz. Ders planları `.txt` formatında, kalan veriler ise `.psv` (Pipe separated values) formatında saklanmakta. Aşağıdaki linklerden, dosyalara ulaşabilir ve okuyabilirsiniz.
+
+- Dersler (_lessons_): https://raw.githubusercontent.com/itu-helper/data/main/lessons.psv
+
+- Dersler (_courses_): https://raw.githubusercontent.com/itu-helper/data/main/courses.psv
+
+- Ders Planları: https://raw.githubusercontent.com/itu-helper/data/main/course_plans.txt
+
+- Bina Kodları: https://raw.githubusercontent.com/itu-helper/data/main/building_codes.psv
+
+- Program Kodları: https://raw.githubusercontent.com/itu-helper/data/main/programme_codes.psv
+
+#### **Python Örneği**
+
+Aşağıdaki kodda _requests_ modülüyle; CRN kullanarak, dersin [bu sayfadaki](https://obs.itu.edu.tr/public/DersProgram) verilerine erişim gösterilmiştir.
+
+```python
+from requests import get
+
+URL = "https://raw.githubusercontent.com/itu-helper/data/main/lessons.psv"
+
+# Dersleri (lessons) oku ve satır satır ayır.
+lines = get(URL).text.split("\n")
+
+# .psv formatından dolayı, her bir satırdaki elementler "|" sembolü ile ayırılıyor.
+# İlk eleman, dersin CRN'si. CRN'lerin "key", satırın tamamının da "value" olduğu bir sözlük oluştur.
+crn_to_lesson = {line.split("|")[0] : line for line in lines}
+
+print(crn_to_lesson["13590"])
+# OUTPUT
+# '13590|BLG 212E|Fiziksel (Yüz yüze)|Gökhan İnce|BBB|Monday|08:30/11:29 - |Z-18|120|111|BLG_LS, BLGE_LS, CEN_LS'
+```
diff --git a/js/README.md b/js/README.md
new file mode 100644
index 0000000..1779cff
--- /dev/null
+++ b/js/README.md
@@ -0,0 +1,33 @@
+
+
+![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/itu-helper/data-updater/refresh_lessons.yml?label=Refreshing%20Lesson&logo=docusign&style=flat-square)
+![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/itu-helper/data-updater/refresh_courses_and_plans.yml?label=Refreshing%20Courses%20%26%20Course%20Plans&logo=docusign&style=flat-square)
+![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/itu-helper/data-updater/refresh_misc.yml?label=Refreshing%20Misc&logo=docusign&style=flat-square)
+![GitHub repo size](https://img.shields.io/github/repo-size/itu-helper/data-updater?label=Repository%20Size&logo=github&style=flat-square)
+![GitHub](https://img.shields.io/github/license/itu-helper/data-updater?label=License&style=flat-square)
+![GitHub issues](https://img.shields.io/github/issues-raw/itu-helper/data-updater?label=Issues&style=flat-square)
+
+# **ITU Helper**
+
+
+
+
+
+
+
+
+_İTÜ'lüler için İTÜ'lülerden_
+
+_ITU Helper_ İstanbul Teknik Üniversitesi öğrencilerine yardım etmek amacıyla ön şart görselleştirme, ders planı oluşturma ve resmi İTÜ sitelerini birleştirme gibi hizmetler sağlayan bir açık kaynaklı websitesidir.
+
+_ITU Helper_'a [_bu adresten_](https://itu-helper.github.io/home/) ulaşabilirsiniz.
+
+
+
+
+
+
+# **itu-helper/sdk - JavaScript SDK**
+
+...
diff --git a/js/dist/bundle.min.js b/js/dist/bundle.min.js
new file mode 100644
index 0000000..78037de
--- /dev/null
+++ b/js/dist/bundle.min.js
@@ -0,0 +1 @@
+class Course{constructor(e,s,t,r,i,c,n,h){this.courseCode=e,this.courseTitle=s,this.lang=t,this.credits=r,this.ects=i,this.classRestrictions=n,this.description=h,this.majorRestrictions="",this.lessons=[],this._createRequirementNames(c)}static createAutoGeneratedCourse(e){return new Course(e,"Auto Generated Course","",0,0,"","","")}_createRequirementNames(e){if(this._requirementNames=[],!e.includes("Yok")&&!(e.includes("planının")||e.includes("Diğer")||e.includes("Özel")||e.includes("için"))){var s=(e=e.replaceAll("veya","\nveya").replaceAll("ve","\nve").replaceAll("(","").replaceAll(")","")).split("\n");for(let e=0;e{}}get courses(){return this._courses.length<=0&&(this._createCourses(),this._courses.forEach((e=>{this.coursesDict[e.courseCode]=e})),this._createLessons(),this._connectAllCourses()),this._courses}get semesters(){return Object.keys(this._semesters).length<=0&&(this.courses,this._createSemesters()),this._semesters}fetchData(){this._fetchTextFile(this.LESSON_PATH,(e=>{this.lesson_lines=e.split("\n"),this._onTextFetchSuccess()})),this._fetchTextFile(this.COURSE_PATH,(e=>{this.course_lines=e.split("\n"),this._onTextFetchSuccess()})),this._fetchTextFile(this.COURSE_PLAN_PATH,(e=>{this.course_plan_lines=e.split("\n"),this._onTextFetchSuccess()}))}_onTextFetchSuccess(){this.fileFetchStatus++,this.fileFetchStatus>=3&&this.onFetchComplete()}_createCourses(){let e=this.course_lines;this._courses=[],this.coursesDict={};for(let s=0;s{e.connectCourses(this)}))}findCourseByCode(e){let s=this.coursesDict[e];if(null==s){if(""===e)return null;s=Course.createAutoGeneratedCourse(e),s.requirements=[],this._courses.push(s),this.coursesDict[e]=s}return s}_createSemesters(){let e="",s="",t="",r=[];this._semesters=[];let i=this.course_plan_lines;for(let c=0;c{r.push(this.findCourseByCode(e))})),i.push(new CourseGroup(r,e[0]))}else{let e=this.findCourseByCode(s);if(null==e)continue;i.push(e)}}r.push(i),8==r.length&&(this._semesters[e][s][t]=r)}}}_fetchTextFile(e,s){fetch(e).then((e=>e.text())).then(s).catch((e=>console.error(e)))}}class Lesson{constructor(e,s,t,r,i,c,n,h,l){this.crn=e,this.teachingMethod=s?.trim(),this.instructor=t?.trim(),this.building=r?.trim(),this.time=c?.trim(),this.day=i?.trim(),this.room=n?.trim(),this.capacity=h,this.enrolled=l}getStartAndEndTime(){const e=this.time.split("/");return[e[0].slice(0,2)+":"+e[0].slice(2),e[1].slice(0,2)+":"+e[1].slice(2)]}getDayAsNumber(){switch(this.day.toLowerCase()){case"pazartesi":return 0;case"salı":return 1;case"çarşamba":return 2;case"perşember":return 3;case"cuma":return 4;case"cumartesi":return 5;case"pazar":return 6;default:return-1}}}
\ No newline at end of file
diff --git a/js/src/course.js b/js/src/course.js
new file mode 100644
index 0000000..fc3f4bd
--- /dev/null
+++ b/js/src/course.js
@@ -0,0 +1,89 @@
+class Course {
+ constructor(code, title, lang, credits, ects, requirementsText, classRestrictions, description) {
+ this.courseCode = code;
+ this.courseTitle = title;
+ this.lang = lang;
+ this.credits = credits;
+ this.ects = ects;
+ this.classRestrictions = classRestrictions;
+ this.description = description;
+
+ this.majorRestrictions = "";
+ this.lessons = [];
+
+ this._createRequirementNames(requirementsText);
+ }
+
+ static createAutoGeneratedCourse(code) {
+ return new Course(code, "Auto Generated Course", "", 0, 0, "", "", "");
+ }
+
+ /**
+ * parses the `requirementsText` and creates `this._requirementNames` array.
+ * @param {string} requirementsText the text written in ITU's site for requirements
+ */
+ _createRequirementNames(requirementsText) {
+ // (MAT 201 MIN DDveya MAT 201E MIN DDveya MAT 210 MIN DDveya MAT 210E MIN DD)ve (EHB 211 MIN DDveya EHB 211E MIN DD)
+ // FIZ 102 MIN DDveya FIZ 102E MIN DDveya EHB 211 MIN DDveya EHB 211E MIN DD
+ this._requirementNames = [];
+
+ // If there are no requirements, return an empty list.
+ if (requirementsText.includes("Yok")) {
+ return;
+ }
+ else if (requirementsText.includes("planının") || requirementsText.includes("Diğer") || requirementsText.includes("Özel") || requirementsText.includes("için")) {
+ // TODO: Implement this.
+ return;
+ }
+
+ requirementsText = requirementsText
+ .replaceAll("veya", "\nveya")
+ .replaceAll("ve", "\nve")
+ .replaceAll("(", "")
+ .replaceAll(")", "");
+
+ var lines = requirementsText.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ var line = lines[i].trim();
+ var words = line.split(" ");
+
+ // If this is the first line, then there
+ // is no "ve" or "veya" in the line.
+ // ex: '(FIZ 101 MIN DD' ...
+ if (i == 0) {
+ this._requirementNames.push([words[0] + " " + words[1]]);
+ continue
+ }
+
+ // If the line contains "ve" or "veya".
+ // ex: 'veya FIZ 101E MIN DD)'
+ // ex2: 've (STA 201 MIN DD'
+ let requirementName = words[1] + " " + words[2];
+ let logicGate = words[0];
+
+ // Append to the last array.
+ if (logicGate == "veya")
+ this._requirementNames[this._requirementNames.length - 1].push(requirementName);
+ // Create a new array.
+ else if (logicGate == "ve")
+ this._requirementNames.push([requirementName]);
+ }
+ }
+
+ /**
+ * creates `this.requirements` array by replacing the names in `this._requirementNames`
+ * with the courses of the given `ITUHelper` object.
+ * @param {ITUHelper} ituHelper
+ */
+ connectCourses(ituHelper) {
+ this.requirements = [];
+ for (let i = 0; i < this._requirementNames.length; i++) {
+ this.requirements.push([]);
+ for (let j = 0; j < this._requirementNames[i].length; j++) {
+ let course = ituHelper.findCourseByCode(this._requirementNames[i][j]);
+ if (course != null)
+ this.requirements[i].push(course);
+ }
+ }
+ }
+}
diff --git a/js/src/course_group.js b/js/src/course_group.js
new file mode 100644
index 0000000..9a7e9c9
--- /dev/null
+++ b/js/src/course_group.js
@@ -0,0 +1,6 @@
+class CourseGroup {
+ constructor (courses, title){
+ this.courses = courses;
+ this.title = title;
+ }
+}
diff --git a/js/src/itu_helper.js b/js/src/itu_helper.js
new file mode 100644
index 0000000..13eee2e
--- /dev/null
+++ b/js/src/itu_helper.js
@@ -0,0 +1,241 @@
+class ITUHelper {
+ LESSON_PATH = "https://raw.githubusercontent.com/itu-helper/data/refs/heads/main/lessons.psv";
+ COURSE_PATH = "https://raw.githubusercontent.com/itu-helper/data/refs/heads/main/courses.psv";
+ COURSE_PLAN_PATH = "https://raw.githubusercontent.com/itu-helper/data/refs/heads/main/course_plans.txt";
+
+ constructor() {
+ this._courses = [];
+ this._semesters = {};
+ this.coursesDict = {};
+
+ this.fileFetchStatus = 0;
+ this.onFetchComplete = () => { };
+ }
+
+ /**
+ * a list of `Course` objects.
+ *
+ * ⚠️NOTE: `fetchData` must be called before accessing this property.
+ */
+ get courses() {
+ if (this._courses.length <= 0) {
+ this._createCourses();
+ this._courses.forEach(course => {
+ this.coursesDict[course.courseCode] = course;
+ });
+ this._createLessons();
+ this._connectAllCourses();
+ }
+
+ return this._courses;
+ }
+
+ /**
+ * a dictionary of dictionaries of dictionaries of arrays of `Course` & `CourseGroup` (Mixed).
+ * Where each array represents a semester.
+ *
+ * Structure:
+ *
+ * `semesters["faculty name"]["programme name"]["iteration name"]`
+ *
+ * Example:
+ *
+ * `semesters["Bilgisayar ve Bilişim Fakültesi"]["Yapay Zeka ve Veri Mühendisliği (% 100 İngilizce)"]["2021-2022 Güz Dönemi Sonrası"]`
+ *
+ * ⚠️NOTE: `fetchData` must be called before accessing this property.
+ */
+ get semesters() {
+ if (Object.keys(this._semesters).length <= 0) {
+ this.courses;
+ this._createSemesters();
+ }
+
+ return this._semesters;
+ }
+
+ /**
+ * fetches the data from itu-helper/data repo, calls `onFetchComplete`
+ * when all files are fetches.
+ */
+ fetchData() {
+ this._fetchTextFile(this.LESSON_PATH, (txt) => {
+ this.lesson_lines = txt.split("\n");
+ this._onTextFetchSuccess();
+ });
+ this._fetchTextFile(this.COURSE_PATH, (txt) => {
+ this.course_lines = txt.split("\n");
+ this._onTextFetchSuccess();
+ });
+ this._fetchTextFile(this.COURSE_PLAN_PATH, (txt) => {
+ this.course_plan_lines = txt.split("\n");
+ this._onTextFetchSuccess();
+ });
+ }
+
+ _onTextFetchSuccess() {
+ this.fileFetchStatus++;
+ if (this.fileFetchStatus >= 3)
+ this.onFetchComplete();
+ }
+
+ /**
+ * processes `course_lines` to create the `courses` array.
+ */
+ _createCourses() {
+ let lines = this.course_lines;
+ this._courses = [];
+ this.coursesDict = {};
+
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i].replace("\r", "");
+ if (line.length == 0) continue;
+
+ let data = line.split("|");
+ if (data.length < 8) continue;
+
+ let course = new Course(data[0], data[1], data[2], parseInt(data[3]), parseInt(data[4]), data[5], data[6], data[7]);
+ this._courses.push(course);
+ }
+ }
+
+ /**
+ * processes `lesson_lines` to create lessons and add them to
+ * corresponding courses of the `courses` array.
+ */
+ _createLessons() {
+ let lines = this.lesson_lines;
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i].replace("\r", "");
+
+ let data = line.split("|");
+ let courseCode = data[1];
+ let majorRest = data[9];
+ let currentLesson = new Lesson(data[0], data[2], data[3], data[4],
+ data[5], data[6], data[7], data[8]);
+
+ let course = this.findCourseByCode(courseCode);
+ if (!course) continue;
+
+ course.lessons.push(currentLesson);
+ course.majorRest = majorRest;
+ }
+ }
+
+ /**
+ * calls the `course.connectCourses` method for all courses in the `courses` array.
+ */
+ _connectAllCourses() {
+ this._courses.forEach(course => {
+ course.connectCourses(this);
+ });
+ }
+
+ /**
+ *
+ * @param {string} courseCode the code of the course, Ex: "MAT 281E"
+ * @returns the corresponding course in the `courses` array,
+ * if the `courseCode` argument is empty returns null. If it is not empty
+ * but a match cannot be found, creates a new course with the given title
+ * and the name `"Auto Generated Course"` and returns it.
+ */
+ findCourseByCode(courseCode) {
+ let course = this.coursesDict[courseCode];
+ if (course == undefined) {
+ if (courseCode === "") return null;
+ course = Course.createAutoGeneratedCourse(courseCode);
+ course.requirements = [];
+ this._courses.push(course);
+ this.coursesDict[courseCode] = course;
+
+ // console.warn("[Course Generation] " + courseCode + " got auto-generated.");
+ }
+
+ return course;
+ }
+
+ /**
+ * processes `course_plan_lines` to create the course plans
+ * and fills it with the courses in the `courses` array.
+ */
+ _createSemesters() {
+ let currentFaculty = "";
+ let currentProgram = "";
+ let currentIteration = "";
+ let currentSemesters = [];
+ this._semesters = [];
+
+ let lines = this.course_plan_lines;
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].replace("\r", "").trim();
+ if (line.includes('# ')) {
+ currentSemesters = [];
+ let hashtagCount = line.split(' ')[0].length;
+ let title = line.slice(hashtagCount + 1).trim();
+ if (hashtagCount == 1) {
+ currentFaculty = title;
+ this._semesters[currentFaculty] = {};
+ }
+ if (hashtagCount == 2) {
+ // Check if the last program had any iterations
+ // If not delete it.
+ if (this._semesters[currentFaculty][currentProgram] != undefined) {
+ if (!Object.keys(this._semesters[currentFaculty][currentProgram]).length)
+ delete this._semesters[currentFaculty][currentProgram];
+ }
+
+ currentProgram = title;
+ this._semesters[currentFaculty][currentProgram] = {};
+ }
+ if (hashtagCount == 3)
+ currentIteration = title;
+ }
+ else {
+ let semester = [];
+ let courses = line.split('=');
+ for (let j = 0; j < courses.length; j++) {
+ let course = courses[j];
+ // Course Group
+ if (course[0] === "[") {
+ course = course.replace("[", "").replace("]", "");
+ let courseGroupData = course.split("*");
+ courseGroupData[1] = courseGroupData[1].replace("(", "").replace(")", "");
+ let selectiveCourseNames = courseGroupData[1].split('|');
+ let selectiveCourses = [];
+ selectiveCourseNames.forEach(selectiveCourseName => {
+ selectiveCourses.push(this.findCourseByCode(selectiveCourseName));
+ });
+ semester.push(new CourseGroup(selectiveCourses, courseGroupData[0]));
+ }
+ // Course
+ else {
+ let courseObject = this.findCourseByCode(course);
+ if (courseObject == null) continue;
+
+ semester.push(courseObject);
+ }
+ }
+
+ currentSemesters.push(semester);
+
+ if (currentSemesters.length == 8)
+ this._semesters[currentFaculty][currentProgram][currentIteration] = currentSemesters;
+ }
+ }
+ }
+
+ /**
+ * @param {string} path path of the text file to fetch.
+ * @param {*} onSuccess the method to call on success.
+ */
+ _fetchTextFile(path, onSuccess) {
+ // $.ajax({
+ // url: path,
+ // type: 'get',
+ // success: onSuccess,
+ // });
+ fetch(path)
+ .then((res) => res.text())
+ .then(onSuccess)
+ .catch((e) => console.error(e));
+ }
+}
diff --git a/js/src/lesson.js b/js/src/lesson.js
new file mode 100644
index 0000000..dd74b0b
--- /dev/null
+++ b/js/src/lesson.js
@@ -0,0 +1,51 @@
+class Lesson {
+ constructor(crn, teachingMethod, instructor, building, day, time, room, capacity, enrolled) {
+ this.crn = crn;
+ this.teachingMethod = teachingMethod?.trim();
+ this.instructor = instructor?.trim();
+ this.building = building?.trim();
+ this.time = time?.trim();
+ this.day = day?.trim();
+ this.room = room?.trim();
+ this.capacity = capacity;
+ this.enrolled = enrolled;
+ }
+
+ /**
+ *
+ * @returns An array with 2 elements, first is the start time, second is the end time.
+ * Example: if `this.time` = `"0830/1129"`, then this returns `["08:30", "11:29"]`
+ */
+ getStartAndEndTime() {
+ const times = this.time.split("/");
+ return [
+ times[0].slice(0, 2) + ":" + times[0].slice(2),
+ times[1].slice(0, 2) + ":" + times[1].slice(2),
+ ];
+ }
+
+ /**
+ * Converts the string `this.day` to a number representing which day of the week it is (Week starts from mondays).
+ * @returns number representing the day of the week. (-1 if cannot be parsed)
+ */
+ getDayAsNumber() {
+ switch (this.day.toLowerCase()) {
+ case "pazartesi":
+ return 0;
+ case "salı":
+ return 1;
+ case "çarşamba":
+ return 2;
+ case "perşember":
+ return 3;
+ case "cuma":
+ return 4;
+ case "cumartesi":
+ return 5;
+ case "pazar":
+ return 6;
+ default:
+ return -1;
+ }
+ }
+}