
Authenticate to Soongsil University LMS and fetch terms, courses, todos, attendance, announcements and scores; maintains session cookies/tokens, supports PEM normalization and progress callbacks.
숭실대학교 LMS(Canvas, LearningX) 및 유세인트(U-Saint)의 학기, 강의, 할 일, 출석, 공지, 시간표, 성적, 채플, 등록금, 장학금, 졸업사정표 등의 정보를 조회하기 위한 Kotlin Multiplatform 라이브러리입니다.
이 README는 **Swift Package Manager(SPM)**를 이용한 iOS 연동과 Gradle을 이용한 Android/Kotlin 연동을 기준으로 작성되었습니다.
iosArm64
macosArm64
현재 소스 빌드에는 JavaScript target과 iOS simulator target(iosX64, iosSimulatorArm64)이 포함되어 있지 않습니다. iOS 앱 연동은 GitHub Release에 배포된 XCFramework를 사용하는 SPM 방식을 권장합니다.
LmsApi 싱글톤과 로그인·조회 콜백 API는 그대로 유지됩니다.parse*, merge*, find*, fetchWebDynproHtml 같은 원본 응답 처리 함수는 내부 구현으로 변경되었습니다. 앱에서는 이 함수들을 직접 호출하지 말고 get* 조회 API를 사용해야 합니다.LmsApi는 프로세스 내에서 로그인 세션, 토큰, 쿠키와 일부 최신 조회 조건을 공유합니다. 새 로그인을 시작하면 이전 사용자의 세션과 캐시를 먼저 비우며, logout도 로그인 상태와 사용자별 캐시를 제거합니다.getTerms, getTodoList, getSubjects 등)는 외부에 콜백 API로 제공됩니다. U-Saint 조회 API는 Kotlin의 suspend 함수와 결과 콜백을 모두 제공합니다.internal 서비스로 분리되었지만 외부 사용법은 변경되지 않았습니다. 내부 구조와 기능 추가 규칙은 내부 구현 가이드를 참고하세요.iOS 앱 프로젝트에서는 Xcode의 Swift Package Manager를 통해 의존성을 추가할 수 있습니다.
File > Add Package Dependencies...를 선택합니다.https://github.com/chlwhdtn03/LMS-API
LmsApi를 선택하고 앱 target에 추가합니다.Swift 파일에서는 다음과 같이 import 합니다.
import LmsApi[!NOTE] SPM은 내부적으로 GitHub Release에 업로드된
LmsApi.xcframework.zip을 내려받아 사용하도록 구성되어 있습니다. SPM에서 설치되는 바이너리 버전은 루트Package.swift의 Release URL을 따르며, Maven 배포 버전과 별도로 갱신될 수 있습니다.
Android 또는 Kotlin Multiplatform(KMP) 프로젝트에서는 Gradle 의존성으로 추가하여 사용할 수 있습니다.
Android 단일 프로젝트 (build.gradle.kts):
dependencies {
implementation("io.github.chlwhdtn03:lms:1.6.3.1")
}Kotlin Multiplatform 프로젝트 (commonMain 의존성):
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.chlwhdtn03:lms:1.6.3.1")
}
}
}LMS 조회 기능과 유세인트(U-Saint) 조회 기능은 모두 LMS 로그인 완료 후 생성된 세션을 공유하여 호출할 수 있습니다. 로그인에 성공하면 학번 정보와 토큰 정보가 내부적으로 캐싱되어 이후 호출되는 API에 자동으로 적용됩니다.
import LmsApi
func performLogin() {
LmsApi.shared.loginLMS(id: "학번", password: "비밀번호") { result in
if result.success {
print("로그인 성공")
} else {
print("로그인 실패: \(result.errorMessage ?? "알 수 없는 오류")")
}
}
}import io.github.chlwhdtn03.LmsApi
fun performLogin() {
LmsApi.loginLMS(id = "학번", password = "비밀번호") { result ->
if (result.success) {
println("로그인 성공")
} else {
println("로그인 실패: ${result.errorMessage}")
}
}
}유세인트 조회 기능은 시간표, 성적, 채플, 등록금, 장학금, 졸업사정표 데이터를 비동기식으로 파싱하여 반환합니다. Kotlin에서는 suspend 함수 또는 결과 콜백을 사용할 수 있으며, iOS에서는 Kotlin/Native가 변환한 비동기 API를 Swift async/await 또는 결과 콜백 형태로 사용할 수 있습니다.
import LmsApi
func loadUSaintInformation() {
Task {
do {
// 1. 시간표 조회
let timetable = try await LmsApi.shared.getTimetable()
print("시간표 학기: \(timetable.year) \(timetable.semester)")
for item in timetable.items {
print("- [\(item.subject)] \(item.classroom) / \(item.professor)")
}
// 2. 성적 상세 조회 (year, semester에 nil을 전달하면 캐싱된 최근 학기 성적을 조회합니다.)
let gradeTable = try await LmsApi.shared.getGradeTable(year: nil, semester: nil)
for grade in gradeTable.items {
print("- \(grade.subjectName): \(grade.grade) (\(grade.credits)학점)")
}
// 3. 성적 요약 조회 (학기별 신청학점, 평점평균, 석차 등)
let gradeSummary = try await LmsApi.shared.getSemesterGradeSummaryTable()
for summary in gradeSummary.items {
print("- \(summary.year)년 \(summary.semester?.name ?? "")학기 평점: \(summary.gpa)")
}
// 4. 채플 출결 및 좌석 조회 (year, semester에 nil을 전달하면 캐싱된 최근 채플 내역을 조회합니다.)
let chapel = try await LmsApi.shared.getChapelTable(year: nil, semester: nil)
print("배정 좌석 번호: \(chapel.seatStatusTable.items.first?.seatNo ?? "없음")")
// 5. 등록금 납부 내역 조회
let tuition = try await LmsApi.shared.getTuitionTable()
for record in tuition.items {
print("- \(record.year) \(record.semester) 납부 금액: \(record.paymentAmount)")
}
// 6. 장학 수혜 내역 조회
let scholarship = try await LmsApi.shared.getScholarshipHistoryTable()
for item in scholarship.items {
print("- \(item.year)학년도 \(item.semester)학기 [\(item.scholarshipName)] 수혜 금액: \(item.actualAmount)")
}
// 7. 졸업사정표 조회
let graduate = try await LmsApi.shared.getGraduateTable()
for row in graduate.items {
print("- \(row.classification) (졸업요건: \(row.standardValue)학점 / 취득: \(row.calculatedValue)학점)")
}
} catch {
print("유세인트 정보 조회 실패: \(error.localizedDescription)")
}
}
}import io.github.chlwhdtn03.LmsApi
import io.github.chlwhdtn03.data.Lms.Semester
fun loadUSaintForAndroid() {
// 1. 시간표 조회
LmsApi.getTimetable { result ->
if (result.success && result.timetable != null) {
println("시간표 학기: ${result.timetable.year} ${result.timetable.semester}")
}
}
// 2. 성적 상세 조회
LmsApi.getGradeTable(year = "2026", semester = Semester.FIRST) { result ->
if (result.success && result.gradeTable != null) {
result.gradeTable.items.forEach { cell ->
println("${cell.subjectName}: ${cell.grade}")
}
}
}
// 3. 성적 요약 조회
LmsApi.getSemesterGradeSummaryTable { result ->
if (result.success && result.summaryTable != null) {
result.summaryTable.items.forEach { summary ->
println("${summary.year}학년도 평점평균: ${summary.gpa}")
}
}
}
// 4. 채플 조회
LmsApi.getChapelTable(year = null, semester = null) { result ->
if (result.success && result.chapelInformation != null) {
println("결석 횟수: ${result.chapelInformation.seatStatusTable.items.firstOrNull()?.absenceCount}")
}
}
// 5. 등록금 납부 내역 조회
LmsApi.getTuitionTable { result ->
if (result.success && result.tuitionTable != null) {
println("마지막 납부액: ${result.tuitionTable.items.firstOrNull()?.paymentAmount}")
}
}
// 6. 장학 수혜 내역 조회
LmsApi.getScholarshipHistoryTable { result ->
if (result.success && result.scholarshipHistoryTable != null) {
println("수혜 장학금명: ${result.scholarshipHistoryTable.items.firstOrNull()?.scholarshipName}")
}
}
// 7. 졸업사정표 조회
LmsApi.getGraduateTable { result ->
if (result.success && result.graduateTable != null) {
println("이수결과: ${result.graduateTable.items.firstOrNull()?.result}")
}
}
}LMS 조회 기능은 학기 목록, 강의 목록, 할 일(과제 및 동영상 시청 기한), 출석, 공지, 제출 및 점수 정보를 조회할 수 있는 기능을 제공합니다.
import LmsApi
func loadLMSTodos() {
LmsApi.shared.getTerms { termsResult in
guard termsResult.success else {
print("학기 조회 실패: \(termsResult.errorMessage ?? "알 수 없는 오류")")
return
}
guard let latestTerm = termsResult.terms.last else {
print("조회 가능한 학기가 없습니다.")
return
}
LmsApi.shared.getTodoList(
term: latestTerm,
loadingState: { progress in
print("진행률: \(Int(progress.floatValue * 100))%")
},
postHogDistinctId: nil
) { subjectsResult in
guard subjectsResult.success else {
print("할 일 조회 실패: \(subjectsResult.errorMessage ?? "알 수 없는 오류")")
return
}
for subject in subjectsResult.subjects {
print("과목: \(subject.name)")
for todo in subject.todoList {
print("- \(todo.title) (마감일: \(todo.due_date))")
}
}
}
}
}import io.github.chlwhdtn03.LmsApi
fun loadLMSForAndroid() {
LmsApi.getTerms { termsResult ->
if (termsResult.success) {
val latestTerm = termsResult.terms.lastOrNull() ?: return@getTerms
LmsApi.getTodoList(
term = latestTerm,
loadingState = { progress ->
println("loading: ${(progress * 100).toInt()}%")
},
completion = { subjectsResult ->
if (subjectsResult.success) {
subjectsResult.subjects.forEach { subject ->
subject.todoList.forEach { todo ->
println("[${subject.name}] ${todo.title} / ${todo.due_date}")
}
}
}
}
)
}
}
}fun loginLMS(id: String, password: String, completion: (LmsLoginResult) -> Unit)LMS 아이디와 비밀번호로 로그인합니다. 새 로그인 시도 전에 기존 세션을 제거하며, 로그인 후 사용자 정보가 실제로 조회된 경우에만 성공 처리합니다. 성공 시 유세인트 세션도 자동으로 공유됩니다.
로그인 여부는 외부에서 읽기만 가능한 LmsApi.isLoggined로 확인할 수 있습니다. 기존 호환성을 위해 현재 철자를 유지합니다.
fun logout(completion: () -> Unit)현재 로그인 상태와 쿠키, 사용자별 성적·채플 최신 조회 조건 캐시를 제거합니다.
fun getTerms(completion: (LmsTermsResult) -> Unit)로그인된 사용자의 학기 목록을 가져옵니다.
fun getTodoList(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)
fun getTodoList(
term: Term,
loadingState: (Float) -> Unit = {},
postHogDistinctId: String?,
completion: (LmsSubjectsResult) -> Unit,
)과목 기본 정보와 할 일 목록(과제, 동영상 등), 제출 정보를 빠르게 파싱하여 가져옵니다.
postHogDistinctId가 없는 오버로드를 사용하거나 null을 전달하면 분석 데이터를 전송하지 않습니다. 식별자를 전달하면 식별자별 하루 한 번 전송 여부를 20% 비율로 샘플링하며, 선택된 경우 Todo 동기화 통계와 항목 상태 스냅샷을 PostHog로 전송합니다. 이를 의도한 앱에서만 사용하세요.
fun getSubjects(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)과목 기본 정보, 할 일, 출석, 공지, 제출 및 점수 데이터를 모두 가져옵니다. 여러 API를 내부적으로 호출하므로 getTodoList에 비해 무겁습니다.
fun getLoginInfo(completion: (LmsLoginInfoResult) -> Unit)로그인한 학생의 이름, 학과, 로그인 ID, 이메일 등의 기본 신원 정보를 가져옵니다.
fun getCookies(completion: (LmsCookiesResult) -> Unit)현재 로그인된 세션의 쿠키 목록을 반환합니다. 외부 서비스와의 연동 시 사용합니다.
// 창업지원단 공지사항 조회
fun loadStartUpNotices(pageNum: Int = 1, completion: (StartUpNoticesResult) -> Unit)
// 장학 공지사항 조회
fun loadScholarships(pageNum: Int = 1, completion: (ScholarshipNoticesResult) -> Unit)suspend fun getTimetable(): Timetable
suspend fun getTimetable(year: String?, semester: Semester?): Timetable
fun getTimetable(completion: (LmsTimetableResult) -> Unit)
fun getTimetable(year: String?, semester: Semester?, completion: (LmsTimetableResult) -> Unit)개인의 유세인트 시간표를 조회합니다. 학년도와 학기를 생략하면 유세인트가 제공하는 기본 조회 기간을 사용합니다.
suspend fun getGradeTable(year: String? = null, semester: Semester? = null): GradeTable
fun getGradeTable(completion: (LmsGradeResult) -> Unit)
fun getGradeTable(year: String?, semester: Semester?, completion: (LmsGradeResult) -> Unit)지정된 학년도 및 학기의 성적 상세 내역을 조회합니다. 파라미터가 모두 null일 경우 캐싱된 최신 학기 성적을 반환합니다.
suspend fun getSemesterGradeSummaryTable(): SemesterGradeSummaryTable
fun getSemesterGradeSummaryTable(completion: (LmsSemesterGradeSummaryResult) -> Unit)전체 학기별 신청학점, 취득학점, 평점평균 및 석차 정보가 담긴 성적 요약 데이터를 조회합니다.
suspend fun getChapelTable(year: String? = null, semester: Semester? = null): ChapelInformation
fun getChapelTable(completion: (LmsChapelResult) -> Unit)
fun getChapelTable(year: String?, semester: Semester?, completion: (LmsChapelResult) -> Unit)지정된 학년도 및 학기의 채플 정보(좌석 번호, 주차별 출결, 결석계 신청 현황)를 조회합니다. (계절학기 조회 불가)
suspend fun getTuitionTable(): TuitionTable
fun getTuitionTable(completion: (LmsTuitionResult) -> Unit)학년도/학기별 등록금 고지액, 장학 감면액, 실납부일자 및 납부 금액 등 등록금 납부 이력을 조회합니다.
suspend fun getScholarshipHistoryTable(): ScholarshipHistoryTable
fun getScholarshipHistoryTable(completion: (LmsScholarshipHistoryResult) -> Unit)학기별 수혜한 장학금 명칭, 지급 방법, 선발 금액 및 실수혜금액 등의 내역을 조회합니다.
suspend fun getGraduateTable(): GraduateTable
fun getGraduateTable(completion: (LmsGraduateTableResult) -> Unit)졸업사정표 상의 이수구분별 졸업 기준 요건 학점, 본인 취득학점, 차이값 및 판정 결과를 조회합니다.
FIRST: 1학기 (코드: "090")SUMMER: 여름학기 (코드: "091")SECOND: 2학기 (코드: "092")WINTER: 겨울학기 (코드: "093")year: 학년도 (예: "2026학년도")semester: 학기 (예: "1학기")items: TimetableCell 리스트
dayOfWeek: 요일 (DayOfWeek Enum)period: 교시 (예: "1 교시")periodTime: 교시 시간 범위 (예: "(08:00-08:50)")subject: 과목명professor: 교수명time: 강의 시간 문자열classroom: 강의실year: 학년도semester: 학기items: GradeCell 리스트
subjectCode: 과목코드subjectName: 과목명classification: 이수구분 (예: "전공기초")credits: 학점 (예: "3.0")grade: 등급 (예: "A+")gradePoint: 평점 (예: "4.5")professor: 교수명items: SemesterGradeSummaryCell 리스트
year: 학년도semester: 학기attemptedCredits: 신청학점earnedCredits: 취득학점pfCredits: P/F학점gpa: 평점평균gpaSum: 평점계arithmeticMean: 산술평균semesterRank: 학기석차totalRank: 전체석차academicWarning: 학사경고여부consultationStatus: 상담여부failedYearStatus: 유급여부year: 학년도semester: 학기seatStatusTable: 좌석 정보 테이블 (classGroup, timetable, classroom, seatNo, absenceCount, gradeResult)attendanceTable: 출결 현황 테이블 (classGroup, date, lectureType, status)absenceTable: 결석계 신청 이력 테이블 (year, semester, detail)items: TuitionCell 리스트
year, semester: 학년도 및 학기grade: 학년(기)registrationType: 등록구분 (예: "정규등록")registrationDate: 납부일자amount: 고지금액reduction: 장학 감면액paymentAmount: 최종 실납부금액items: ScholarshipHistoryCell 리스트
year, semester: 장학금 지급 학년도 및 학기scholarshipName: 장학금명paymentMethod: 지급방법 (예: "고지서 감면")processStatus: 처리 상태selectedAmount: 선발금액actualAmount: 실수혜금액redeemedAmount: 환수금액replacedAmount: 교체금액replacedScholarshipName: 교체장학금명workDepartment: 근로부서items: GraduateTableCell 리스트
classification: 이수구분 (예: "전공선택")requirement: 졸업요건standardValue: 기준학점calculatedValue: 취득학점difference: 차이값result: 이수 여부 판정 (예: "합격", "미필")id: 학기 고유 IDname: 학기명start_at: 시작 시각end_at: 종료 시각id: 과목 고유 IDtermId: 학기 IDtermName: 학기명name: 과목명professor: 담당 교수totalStudents: 수강 인원todoList: 과제 및 할 일 목록attendances: 출석 기록 리스트discussions: 공지사항 목록submissions: 과제 제출 정보scoredAssignments: 평가 및 획득한 점수 리스트component_type: 항목 타입 (예: assignment, commons)assignment_id: 과제 고유 IDtitle: 제목due_date: 마감 기한assignment_id: 과제 고유 IDattachments: 첨부파일 목록attempt: 제출 시도 횟수cached_due_date: 마감 시각late: 지각 여부preview_url: 제출 파일 미리보기 URLsubmitted_at: 제출 시각submission_type: 제출 유형workflow_state: 제출 상태 (예: submitted, graded, unsubmitted)score: 획득 점수파서와 공개 범위 등 외부 서버가 필요 없는 테스트는 일반 JVM 테스트로 실행합니다.
./gradlew :library:jvmTest실제 LMS/U-Saint 네트워크 API와 각 콜백 오버로드를 함께 검증하려면 테스트 계정을 환경 변수로 전달합니다. 계정 정보가 없으면 통합 테스트는 외부 서버를 호출하지 않고 종료됩니다.
LMS_TEST_ID="학번" \
LMS_TEST_PASSWORD="비밀번호" \
./gradlew :library:jvmTest \
--tests "io.github.chlwhdtn03.LmsApiFullIntegrationTest"필요하면 LMS_TEST_TERM_ID, LMS_TEST_YEAR, LMS_TEST_SEMESTER도 지정할 수 있습니다. LMS_TEST_SEMESTER는 FIRST, SECOND, 090, 092, 1학기, 2학기 형식을 지원합니다. 실제 계정 정보는 소스 코드나 커밋에 저장하지 마세요.
이 저장소의 루트에 있는 Package.swift는 Kotlin/Native 컴파일 결과물인 XCFramework를 바이너리 타겟으로 래핑하여 배포하도록 설정되어 있습니다.
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "LmsApi",
platforms: [
.iOS(.v14),
],
products: [
.library(name: "LmsApi", targets: ["LmsApi"])
],
targets: [
.binaryTarget(
name: "LmsApi",
url: "https://github.com/chlwhdtn03/LMS-API/releases/download/<release-tag>/LmsApi.xcframework.zip",
checksum: "<checksum calculated for the ZIP file>"
)
]
)체크리스트:
Package.swift URL의 <release-tag>가 일치해야 합니다.checksum은 빌드 완료된 LmsApi.xcframework.zip 파일 기준으로 계산된 체크섬이어야 합니다.swift package compute-checksum LmsApi.xcframework.zip./gradlew :library:assembleLmsApiReleaseXCFramework 실행LmsApi.xcframework를 LmsApi.xcframework.zip으로 압축swift package compute-checksum LmsApi.xcframework.zip 실행하여 체크섬 확보Package.swift의 checksum 값 및 URL 경로 업데이트 후 커밋LmsApi.xcframework.zip을 릴리스 에셋으로 업로드바이너리 프레임워크를 수동으로 내려받거나 직접 로컬 빌드하여 추가할 수도 있습니다.
./gradlew :library:assembleLmsApiReleaseXCFrameworklibrary/build/XCFrameworks/release/LmsApi.xcframework 폴더를 Xcode의 Project Navigator로 드래그하여 드롭합니다.General > Frameworks, Libraries, and Embedded Content 항목에서 LmsApi.xcframework를 등록합니다.Do Not Embed를 선택합니다.현재 소스 설정으로 직접 빌드한 XCFramework에는 iosArm64 slice만 포함되므로 실제 iOS 기기용입니다.
getTerms, getTodoList, getSubjects 및 모든 유세인트(U-Saint) API는 반드시 loginLMS 인증이 완료된 후에 정상 호출 가능합니다.object LmsApi가 싱글톤 객체로 변환되어 LmsApi.shared 형태로 접근합니다.LmsApi는 단일 사용자 세션을 공유하므로 같은 프로세스에서 여러 계정의 요청을 동시에 처리하는 용도로 사용할 수 없습니다.loadingState 콜백 및 비동기 결과 수신 스레드는 메인(UI) 스레드를 보장하지 않습니다. SwiftUI/UIKit/Compose 등 화면 렌더링에 반영할 경우 메인 디스패처/스레드로의 컨텍스트 스위칭이 필요합니다.숭실대학교 LMS(Canvas, LearningX) 및 유세인트(U-Saint)의 학기, 강의, 할 일, 출석, 공지, 시간표, 성적, 채플, 등록금, 장학금, 졸업사정표 등의 정보를 조회하기 위한 Kotlin Multiplatform 라이브러리입니다.
이 README는 **Swift Package Manager(SPM)**를 이용한 iOS 연동과 Gradle을 이용한 Android/Kotlin 연동을 기준으로 작성되었습니다.
iosArm64
macosArm64
현재 소스 빌드에는 JavaScript target과 iOS simulator target(iosX64, iosSimulatorArm64)이 포함되어 있지 않습니다. iOS 앱 연동은 GitHub Release에 배포된 XCFramework를 사용하는 SPM 방식을 권장합니다.
LmsApi 싱글톤과 로그인·조회 콜백 API는 그대로 유지됩니다.parse*, merge*, find*, fetchWebDynproHtml 같은 원본 응답 처리 함수는 내부 구현으로 변경되었습니다. 앱에서는 이 함수들을 직접 호출하지 말고 get* 조회 API를 사용해야 합니다.LmsApi는 프로세스 내에서 로그인 세션, 토큰, 쿠키와 일부 최신 조회 조건을 공유합니다. 새 로그인을 시작하면 이전 사용자의 세션과 캐시를 먼저 비우며, logout도 로그인 상태와 사용자별 캐시를 제거합니다.getTerms, getTodoList, getSubjects 등)는 외부에 콜백 API로 제공됩니다. U-Saint 조회 API는 Kotlin의 suspend 함수와 결과 콜백을 모두 제공합니다.internal 서비스로 분리되었지만 외부 사용법은 변경되지 않았습니다. 내부 구조와 기능 추가 규칙은 내부 구현 가이드를 참고하세요.iOS 앱 프로젝트에서는 Xcode의 Swift Package Manager를 통해 의존성을 추가할 수 있습니다.
File > Add Package Dependencies...를 선택합니다.https://github.com/chlwhdtn03/LMS-API
LmsApi를 선택하고 앱 target에 추가합니다.Swift 파일에서는 다음과 같이 import 합니다.
import LmsApi[!NOTE] SPM은 내부적으로 GitHub Release에 업로드된
LmsApi.xcframework.zip을 내려받아 사용하도록 구성되어 있습니다. SPM에서 설치되는 바이너리 버전은 루트Package.swift의 Release URL을 따르며, Maven 배포 버전과 별도로 갱신될 수 있습니다.
Android 또는 Kotlin Multiplatform(KMP) 프로젝트에서는 Gradle 의존성으로 추가하여 사용할 수 있습니다.
Android 단일 프로젝트 (build.gradle.kts):
dependencies {
implementation("io.github.chlwhdtn03:lms:1.6.3.1")
}Kotlin Multiplatform 프로젝트 (commonMain 의존성):
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.chlwhdtn03:lms:1.6.3.1")
}
}
}LMS 조회 기능과 유세인트(U-Saint) 조회 기능은 모두 LMS 로그인 완료 후 생성된 세션을 공유하여 호출할 수 있습니다. 로그인에 성공하면 학번 정보와 토큰 정보가 내부적으로 캐싱되어 이후 호출되는 API에 자동으로 적용됩니다.
import LmsApi
func performLogin() {
LmsApi.shared.loginLMS(id: "학번", password: "비밀번호") { result in
if result.success {
print("로그인 성공")
} else {
print("로그인 실패: \(result.errorMessage ?? "알 수 없는 오류")")
}
}
}import io.github.chlwhdtn03.LmsApi
fun performLogin() {
LmsApi.loginLMS(id = "학번", password = "비밀번호") { result ->
if (result.success) {
println("로그인 성공")
} else {
println("로그인 실패: ${result.errorMessage}")
}
}
}유세인트 조회 기능은 시간표, 성적, 채플, 등록금, 장학금, 졸업사정표 데이터를 비동기식으로 파싱하여 반환합니다. Kotlin에서는 suspend 함수 또는 결과 콜백을 사용할 수 있으며, iOS에서는 Kotlin/Native가 변환한 비동기 API를 Swift async/await 또는 결과 콜백 형태로 사용할 수 있습니다.
import LmsApi
func loadUSaintInformation() {
Task {
do {
// 1. 시간표 조회
let timetable = try await LmsApi.shared.getTimetable()
print("시간표 학기: \(timetable.year) \(timetable.semester)")
for item in timetable.items {
print("- [\(item.subject)] \(item.classroom) / \(item.professor)")
}
// 2. 성적 상세 조회 (year, semester에 nil을 전달하면 캐싱된 최근 학기 성적을 조회합니다.)
let gradeTable = try await LmsApi.shared.getGradeTable(year: nil, semester: nil)
for grade in gradeTable.items {
print("- \(grade.subjectName): \(grade.grade) (\(grade.credits)학점)")
}
// 3. 성적 요약 조회 (학기별 신청학점, 평점평균, 석차 등)
let gradeSummary = try await LmsApi.shared.getSemesterGradeSummaryTable()
for summary in gradeSummary.items {
print("- \(summary.year)년 \(summary.semester?.name ?? "")학기 평점: \(summary.gpa)")
}
// 4. 채플 출결 및 좌석 조회 (year, semester에 nil을 전달하면 캐싱된 최근 채플 내역을 조회합니다.)
let chapel = try await LmsApi.shared.getChapelTable(year: nil, semester: nil)
print("배정 좌석 번호: \(chapel.seatStatusTable.items.first?.seatNo ?? "없음")")
// 5. 등록금 납부 내역 조회
let tuition = try await LmsApi.shared.getTuitionTable()
for record in tuition.items {
print("- \(record.year) \(record.semester) 납부 금액: \(record.paymentAmount)")
}
// 6. 장학 수혜 내역 조회
let scholarship = try await LmsApi.shared.getScholarshipHistoryTable()
for item in scholarship.items {
print("- \(item.year)학년도 \(item.semester)학기 [\(item.scholarshipName)] 수혜 금액: \(item.actualAmount)")
}
// 7. 졸업사정표 조회
let graduate = try await LmsApi.shared.getGraduateTable()
for row in graduate.items {
print("- \(row.classification) (졸업요건: \(row.standardValue)학점 / 취득: \(row.calculatedValue)학점)")
}
} catch {
print("유세인트 정보 조회 실패: \(error.localizedDescription)")
}
}
}import io.github.chlwhdtn03.LmsApi
import io.github.chlwhdtn03.data.Lms.Semester
fun loadUSaintForAndroid() {
// 1. 시간표 조회
LmsApi.getTimetable { result ->
if (result.success && result.timetable != null) {
println("시간표 학기: ${result.timetable.year} ${result.timetable.semester}")
}
}
// 2. 성적 상세 조회
LmsApi.getGradeTable(year = "2026", semester = Semester.FIRST) { result ->
if (result.success && result.gradeTable != null) {
result.gradeTable.items.forEach { cell ->
println("${cell.subjectName}: ${cell.grade}")
}
}
}
// 3. 성적 요약 조회
LmsApi.getSemesterGradeSummaryTable { result ->
if (result.success && result.summaryTable != null) {
result.summaryTable.items.forEach { summary ->
println("${summary.year}학년도 평점평균: ${summary.gpa}")
}
}
}
// 4. 채플 조회
LmsApi.getChapelTable(year = null, semester = null) { result ->
if (result.success && result.chapelInformation != null) {
println("결석 횟수: ${result.chapelInformation.seatStatusTable.items.firstOrNull()?.absenceCount}")
}
}
// 5. 등록금 납부 내역 조회
LmsApi.getTuitionTable { result ->
if (result.success && result.tuitionTable != null) {
println("마지막 납부액: ${result.tuitionTable.items.firstOrNull()?.paymentAmount}")
}
}
// 6. 장학 수혜 내역 조회
LmsApi.getScholarshipHistoryTable { result ->
if (result.success && result.scholarshipHistoryTable != null) {
println("수혜 장학금명: ${result.scholarshipHistoryTable.items.firstOrNull()?.scholarshipName}")
}
}
// 7. 졸업사정표 조회
LmsApi.getGraduateTable { result ->
if (result.success && result.graduateTable != null) {
println("이수결과: ${result.graduateTable.items.firstOrNull()?.result}")
}
}
}LMS 조회 기능은 학기 목록, 강의 목록, 할 일(과제 및 동영상 시청 기한), 출석, 공지, 제출 및 점수 정보를 조회할 수 있는 기능을 제공합니다.
import LmsApi
func loadLMSTodos() {
LmsApi.shared.getTerms { termsResult in
guard termsResult.success else {
print("학기 조회 실패: \(termsResult.errorMessage ?? "알 수 없는 오류")")
return
}
guard let latestTerm = termsResult.terms.last else {
print("조회 가능한 학기가 없습니다.")
return
}
LmsApi.shared.getTodoList(
term: latestTerm,
loadingState: { progress in
print("진행률: \(Int(progress.floatValue * 100))%")
},
postHogDistinctId: nil
) { subjectsResult in
guard subjectsResult.success else {
print("할 일 조회 실패: \(subjectsResult.errorMessage ?? "알 수 없는 오류")")
return
}
for subject in subjectsResult.subjects {
print("과목: \(subject.name)")
for todo in subject.todoList {
print("- \(todo.title) (마감일: \(todo.due_date))")
}
}
}
}
}import io.github.chlwhdtn03.LmsApi
fun loadLMSForAndroid() {
LmsApi.getTerms { termsResult ->
if (termsResult.success) {
val latestTerm = termsResult.terms.lastOrNull() ?: return@getTerms
LmsApi.getTodoList(
term = latestTerm,
loadingState = { progress ->
println("loading: ${(progress * 100).toInt()}%")
},
completion = { subjectsResult ->
if (subjectsResult.success) {
subjectsResult.subjects.forEach { subject ->
subject.todoList.forEach { todo ->
println("[${subject.name}] ${todo.title} / ${todo.due_date}")
}
}
}
}
)
}
}
}fun loginLMS(id: String, password: String, completion: (LmsLoginResult) -> Unit)LMS 아이디와 비밀번호로 로그인합니다. 새 로그인 시도 전에 기존 세션을 제거하며, 로그인 후 사용자 정보가 실제로 조회된 경우에만 성공 처리합니다. 성공 시 유세인트 세션도 자동으로 공유됩니다.
로그인 여부는 외부에서 읽기만 가능한 LmsApi.isLoggined로 확인할 수 있습니다. 기존 호환성을 위해 현재 철자를 유지합니다.
fun logout(completion: () -> Unit)현재 로그인 상태와 쿠키, 사용자별 성적·채플 최신 조회 조건 캐시를 제거합니다.
fun getTerms(completion: (LmsTermsResult) -> Unit)로그인된 사용자의 학기 목록을 가져옵니다.
fun getTodoList(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)
fun getTodoList(
term: Term,
loadingState: (Float) -> Unit = {},
postHogDistinctId: String?,
completion: (LmsSubjectsResult) -> Unit,
)과목 기본 정보와 할 일 목록(과제, 동영상 등), 제출 정보를 빠르게 파싱하여 가져옵니다.
postHogDistinctId가 없는 오버로드를 사용하거나 null을 전달하면 분석 데이터를 전송하지 않습니다. 식별자를 전달하면 식별자별 하루 한 번 전송 여부를 20% 비율로 샘플링하며, 선택된 경우 Todo 동기화 통계와 항목 상태 스냅샷을 PostHog로 전송합니다. 이를 의도한 앱에서만 사용하세요.
fun getSubjects(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)과목 기본 정보, 할 일, 출석, 공지, 제출 및 점수 데이터를 모두 가져옵니다. 여러 API를 내부적으로 호출하므로 getTodoList에 비해 무겁습니다.
fun getLoginInfo(completion: (LmsLoginInfoResult) -> Unit)로그인한 학생의 이름, 학과, 로그인 ID, 이메일 등의 기본 신원 정보를 가져옵니다.
fun getCookies(completion: (LmsCookiesResult) -> Unit)현재 로그인된 세션의 쿠키 목록을 반환합니다. 외부 서비스와의 연동 시 사용합니다.
// 창업지원단 공지사항 조회
fun loadStartUpNotices(pageNum: Int = 1, completion: (StartUpNoticesResult) -> Unit)
// 장학 공지사항 조회
fun loadScholarships(pageNum: Int = 1, completion: (ScholarshipNoticesResult) -> Unit)suspend fun getTimetable(): Timetable
suspend fun getTimetable(year: String?, semester: Semester?): Timetable
fun getTimetable(completion: (LmsTimetableResult) -> Unit)
fun getTimetable(year: String?, semester: Semester?, completion: (LmsTimetableResult) -> Unit)개인의 유세인트 시간표를 조회합니다. 학년도와 학기를 생략하면 유세인트가 제공하는 기본 조회 기간을 사용합니다.
suspend fun getGradeTable(year: String? = null, semester: Semester? = null): GradeTable
fun getGradeTable(completion: (LmsGradeResult) -> Unit)
fun getGradeTable(year: String?, semester: Semester?, completion: (LmsGradeResult) -> Unit)지정된 학년도 및 학기의 성적 상세 내역을 조회합니다. 파라미터가 모두 null일 경우 캐싱된 최신 학기 성적을 반환합니다.
suspend fun getSemesterGradeSummaryTable(): SemesterGradeSummaryTable
fun getSemesterGradeSummaryTable(completion: (LmsSemesterGradeSummaryResult) -> Unit)전체 학기별 신청학점, 취득학점, 평점평균 및 석차 정보가 담긴 성적 요약 데이터를 조회합니다.
suspend fun getChapelTable(year: String? = null, semester: Semester? = null): ChapelInformation
fun getChapelTable(completion: (LmsChapelResult) -> Unit)
fun getChapelTable(year: String?, semester: Semester?, completion: (LmsChapelResult) -> Unit)지정된 학년도 및 학기의 채플 정보(좌석 번호, 주차별 출결, 결석계 신청 현황)를 조회합니다. (계절학기 조회 불가)
suspend fun getTuitionTable(): TuitionTable
fun getTuitionTable(completion: (LmsTuitionResult) -> Unit)학년도/학기별 등록금 고지액, 장학 감면액, 실납부일자 및 납부 금액 등 등록금 납부 이력을 조회합니다.
suspend fun getScholarshipHistoryTable(): ScholarshipHistoryTable
fun getScholarshipHistoryTable(completion: (LmsScholarshipHistoryResult) -> Unit)학기별 수혜한 장학금 명칭, 지급 방법, 선발 금액 및 실수혜금액 등의 내역을 조회합니다.
suspend fun getGraduateTable(): GraduateTable
fun getGraduateTable(completion: (LmsGraduateTableResult) -> Unit)졸업사정표 상의 이수구분별 졸업 기준 요건 학점, 본인 취득학점, 차이값 및 판정 결과를 조회합니다.
FIRST: 1학기 (코드: "090")SUMMER: 여름학기 (코드: "091")SECOND: 2학기 (코드: "092")WINTER: 겨울학기 (코드: "093")year: 학년도 (예: "2026학년도")semester: 학기 (예: "1학기")items: TimetableCell 리스트
dayOfWeek: 요일 (DayOfWeek Enum)period: 교시 (예: "1 교시")periodTime: 교시 시간 범위 (예: "(08:00-08:50)")subject: 과목명professor: 교수명time: 강의 시간 문자열classroom: 강의실year: 학년도semester: 학기items: GradeCell 리스트
subjectCode: 과목코드subjectName: 과목명classification: 이수구분 (예: "전공기초")credits: 학점 (예: "3.0")grade: 등급 (예: "A+")gradePoint: 평점 (예: "4.5")professor: 교수명items: SemesterGradeSummaryCell 리스트
year: 학년도semester: 학기attemptedCredits: 신청학점earnedCredits: 취득학점pfCredits: P/F학점gpa: 평점평균gpaSum: 평점계arithmeticMean: 산술평균semesterRank: 학기석차totalRank: 전체석차academicWarning: 학사경고여부consultationStatus: 상담여부failedYearStatus: 유급여부year: 학년도semester: 학기seatStatusTable: 좌석 정보 테이블 (classGroup, timetable, classroom, seatNo, absenceCount, gradeResult)attendanceTable: 출결 현황 테이블 (classGroup, date, lectureType, status)absenceTable: 결석계 신청 이력 테이블 (year, semester, detail)items: TuitionCell 리스트
year, semester: 학년도 및 학기grade: 학년(기)registrationType: 등록구분 (예: "정규등록")registrationDate: 납부일자amount: 고지금액reduction: 장학 감면액paymentAmount: 최종 실납부금액items: ScholarshipHistoryCell 리스트
year, semester: 장학금 지급 학년도 및 학기scholarshipName: 장학금명paymentMethod: 지급방법 (예: "고지서 감면")processStatus: 처리 상태selectedAmount: 선발금액actualAmount: 실수혜금액redeemedAmount: 환수금액replacedAmount: 교체금액replacedScholarshipName: 교체장학금명workDepartment: 근로부서items: GraduateTableCell 리스트
classification: 이수구분 (예: "전공선택")requirement: 졸업요건standardValue: 기준학점calculatedValue: 취득학점difference: 차이값result: 이수 여부 판정 (예: "합격", "미필")id: 학기 고유 IDname: 학기명start_at: 시작 시각end_at: 종료 시각id: 과목 고유 IDtermId: 학기 IDtermName: 학기명name: 과목명professor: 담당 교수totalStudents: 수강 인원todoList: 과제 및 할 일 목록attendances: 출석 기록 리스트discussions: 공지사항 목록submissions: 과제 제출 정보scoredAssignments: 평가 및 획득한 점수 리스트component_type: 항목 타입 (예: assignment, commons)assignment_id: 과제 고유 IDtitle: 제목due_date: 마감 기한assignment_id: 과제 고유 IDattachments: 첨부파일 목록attempt: 제출 시도 횟수cached_due_date: 마감 시각late: 지각 여부preview_url: 제출 파일 미리보기 URLsubmitted_at: 제출 시각submission_type: 제출 유형workflow_state: 제출 상태 (예: submitted, graded, unsubmitted)score: 획득 점수파서와 공개 범위 등 외부 서버가 필요 없는 테스트는 일반 JVM 테스트로 실행합니다.
./gradlew :library:jvmTest실제 LMS/U-Saint 네트워크 API와 각 콜백 오버로드를 함께 검증하려면 테스트 계정을 환경 변수로 전달합니다. 계정 정보가 없으면 통합 테스트는 외부 서버를 호출하지 않고 종료됩니다.
LMS_TEST_ID="학번" \
LMS_TEST_PASSWORD="비밀번호" \
./gradlew :library:jvmTest \
--tests "io.github.chlwhdtn03.LmsApiFullIntegrationTest"필요하면 LMS_TEST_TERM_ID, LMS_TEST_YEAR, LMS_TEST_SEMESTER도 지정할 수 있습니다. LMS_TEST_SEMESTER는 FIRST, SECOND, 090, 092, 1학기, 2학기 형식을 지원합니다. 실제 계정 정보는 소스 코드나 커밋에 저장하지 마세요.
이 저장소의 루트에 있는 Package.swift는 Kotlin/Native 컴파일 결과물인 XCFramework를 바이너리 타겟으로 래핑하여 배포하도록 설정되어 있습니다.
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "LmsApi",
platforms: [
.iOS(.v14),
],
products: [
.library(name: "LmsApi", targets: ["LmsApi"])
],
targets: [
.binaryTarget(
name: "LmsApi",
url: "https://github.com/chlwhdtn03/LMS-API/releases/download/<release-tag>/LmsApi.xcframework.zip",
checksum: "<checksum calculated for the ZIP file>"
)
]
)체크리스트:
Package.swift URL의 <release-tag>가 일치해야 합니다.checksum은 빌드 완료된 LmsApi.xcframework.zip 파일 기준으로 계산된 체크섬이어야 합니다.swift package compute-checksum LmsApi.xcframework.zip./gradlew :library:assembleLmsApiReleaseXCFramework 실행LmsApi.xcframework를 LmsApi.xcframework.zip으로 압축swift package compute-checksum LmsApi.xcframework.zip 실행하여 체크섬 확보Package.swift의 checksum 값 및 URL 경로 업데이트 후 커밋LmsApi.xcframework.zip을 릴리스 에셋으로 업로드바이너리 프레임워크를 수동으로 내려받거나 직접 로컬 빌드하여 추가할 수도 있습니다.
./gradlew :library:assembleLmsApiReleaseXCFrameworklibrary/build/XCFrameworks/release/LmsApi.xcframework 폴더를 Xcode의 Project Navigator로 드래그하여 드롭합니다.General > Frameworks, Libraries, and Embedded Content 항목에서 LmsApi.xcframework를 등록합니다.Do Not Embed를 선택합니다.현재 소스 설정으로 직접 빌드한 XCFramework에는 iosArm64 slice만 포함되므로 실제 iOS 기기용입니다.
getTerms, getTodoList, getSubjects 및 모든 유세인트(U-Saint) API는 반드시 loginLMS 인증이 완료된 후에 정상 호출 가능합니다.object LmsApi가 싱글톤 객체로 변환되어 LmsApi.shared 형태로 접근합니다.LmsApi는 단일 사용자 세션을 공유하므로 같은 프로세스에서 여러 계정의 요청을 동시에 처리하는 용도로 사용할 수 없습니다.loadingState 콜백 및 비동기 결과 수신 스레드는 메인(UI) 스레드를 보장하지 않습니다. SwiftUI/UIKit/Compose 등 화면 렌더링에 반영할 경우 메인 디스패처/스레드로의 컨텍스트 스위칭이 필요합니다.