LMS-API
1.5.4indexedAuthenticate to Soongsil University LMS and fetch terms, courses, todos, attendance, announcements and scores; maintains session cookies/tokens, supports PEM normalization and progress callbacks.
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) 및 숭실사이버대학교(KCU)의 학기, 강의, 할 일, 출석, 공지, 수강편람, 시간표, 성적, 채플, 등록금, 장학금, 졸업사정표 등의 정보를 조회하기 위한 Kotlin Multiplatform 라이브러리입니다.
이 README는 **Swift Package Manager(SPM)**를 이용한 iOS 연동과 Gradle을 이용한 Android/Kotlin 연동을 기준으로 작성되었습니다.
iosArm64macosArm64현재 소스 빌드에는 JavaScript target이 포함되어 있지 않습니다. iOS는 실제 기기(iosArm64)와 시뮬레이터(iosX64, iosSimulatorArm64)를 지원합니다. iOS 앱 연동은 GitHub Release에 배포된 XCFramework를 사용하는 SPM 방식을 권장합니다.
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.12")
}
Kotlin Multiplatform 프로젝트 (commonMain 의존성):
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.chlwhdtn03:lms:1.6.12")
}
}
}
LMS 조회 기능과 대부분의 유세인트(U-Saint) 조회 기능은 LMS 로그인 완료 후 생성된 세션을 공유하여 호출할 수 있습니다. 로그인에 성공하면 학번 정보와 토큰 정보가 내부적으로 캐싱되어 이후 호출되는 API에 자동으로 적용됩니다. 공개 수강편람 조회는 예외로, 로그인 없이 사용할 수 있습니다.
[!NOTE] 숭실사이버대학교(KCU)는 별도 도메인(
portal.kcu.ac,lms.kcu.ac)과 세션을 사용하므로,LmsApi와 무관하게 CyberApi를 통해 독립적으로 로그인하고 조회합니다.
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 또는 결과 콜백 형태로 사용할 수 있습니다.
수강편람은 조회 유형별 콤보박스가 서로 연동되므로 먼저 옵션을 받고, 선택한 key를
filterKeys에 같은 순서로 추가합니다. 검색 결과는 서버의 500줄 단위 페이지를 모두 합쳐
반환합니다.
val baseQuery = CourseCatalogQuery(
year = "2026",
semester = Semester.FIRST,
category = CourseCatalogCategory.DEPARTMENT,
)
val collegeOptions = LmsApi.getCourseCatalogSearchOptions(baseQuery)
val collegeKey = collegeOptions.filters[].options.first().key
departmentOptions = LmsApi.getCourseCatalogSearchOptions(
baseQuery.copy(filterKeys = listOf(collegeKey)),
)
departmentKey = departmentOptions.filters[].options.first().key
courses = LmsApi.getCourseCatalogTable(
baseQuery.copy(filterKeys = listOf(collegeKey, departmentKey)),
)
selectedCourse = courses.items.first()
pdfBytes: ByteArray = selectedCourse.loadPlan()
preRegistration = LmsApi.getPreRegistrationTable()
preRegistrationPdf: ByteArray = preRegistration.items.first().loadPlan()
CourseCatalogCategory.SUBJECT와 PROFESSOR는 keyword를 사용합니다. 버튼형 계획서는
재사용할 수 있는 링크가 아니므로 목록의 plan은 빈 문자열일 수 있습니다. Android와
iOS에서는 과목의 loadPlan()을 호출하면 내부에서 일회성 OZ Viewer를 새로 열고 PDF가
완성된 뒤 바이트를 반환합니다. 이 작업은 네트워크 요청과 보고서 렌더링을 포함하므로 UI
로딩 상태를 표시하는 것이 좋습니다. PDF 크기와 생성 메모리를 줄이기 위해 폰트 임베딩은
비활성화되어 있으므로, 특수 폰트가 사용된 계획서는 플랫폼의 대체 폰트로 표시될 수 있습니다.
Android에서는 OZ 콜백의 전체 문자열을 Kotlin으로 넘기지 않고 WebView의 ArrayBuffer를
로 직접 수신합니다. JVM과 macOS에서는 PDF 로드를 지원하지 않습니다.
iOS XCFramework에서는 loadPlan(completionHandler:)로 노출되며 결과 형식은
KotlinByteArray입니다. Swift의 Data로 변환하면 Apple 기본 프레임워크인
PDFKit의 PDFDocument(data:)와
PDFView로 앱 내부 네이티브 PDF 뷰어를 구성할 수 있습니다. 별도의 외부 PDF 뷰어
라이브러리는 필요하지 않습니다.
SwiftUI에서는 PDFView를 UIViewRepresentable로 감싸서 사용할 수 있습니다.
KotlinByteArray.toData()는 Swift에서 각 바이트를 읽어 새 Data를 만드는 과정이므로 PDF가
큰 경우 추가 복사 비용이 발생합니다. 변환 중에도 로딩 상태를 유지하고, 같은 PDF는 변환한
Data 또는 임시 파일을 재사용하는 것이 좋습니다. 추후 iOS 전용 API가 NSData를 직접
반환하도록 추가된다면 이 변환 단계는 생략할 수 있지만, 현재 공개 API의 반환 타입은
KotlinByteArray입니다.
LMS 조회 기능은 학기 목록, 강의 목록, 할 일(과제 및 동영상 시청 기한), 출석, 공지, 제출 및 점수 정보를 조회할 수 있는 기능을 제공합니다.
import io.github.chlwhdtn03.LmsApi
fun loadLMSForAndroid() {
LmsApi.getTerms { termsResult ->
if (termsResult.success) {
val latestTerm = termsResult.terms.lastOrNull() ?:
LmsApi.getTodoList(
term = latestTerm,
loadingState = { progress ->
println()
},
completion = { subjectsResult ->
(subjectsResult.success) {
subjectsResult.subjects.forEach { subject ->
subject.todoList.forEach { todo ->
println()
}
}
}
}
)
}
}
}
숭실사이버대학교 포털(portal.kcu.ac) 및 LMS(lms.kcu.ac)에 로그인하여 수강 과목 목록과 주차별 강의 수강일람(출석 현황, 진도율, 학습 시간, 완료 여부 등)을 조회할 수 있습니다.
CyberApi는 숭실대학교 LMS 및 유세인트(LmsApi)와 별개의 도메인과 시스템을 사용하므로, 세션 및 쿠키를 공유하지 않는 독립적인 싱글톤 객체입니다.
iOS(Swift)에서는 Kotlin object CyberApi가 CyberApi.shared로 접근되며, async/await 또는 결과 콜백 방식을 모두 지원합니다.
Android/Kotlin에서는 코루틴(suspend) 또는 결과 콜백 오버로드를 모두 지원합니다.
import io.github.chlwhdtn03.CyberApi
fun loadCyberWithCallback() {
CyberApi.login(id = "아이디", password = "비밀번호") { loginResult ->
(loginResult.success) {
CyberApi.getSubjects { subjectsResult ->
(subjectsResult.success) {
firstSubject = subjectsResult.subjects.firstOrNull() ?:
CyberApi.getWeeklyLectures(firstSubject) { lecturesResult ->
(lecturesResult.success) {
println()
}
}
}
}
} {
println()
}
}
}
LmsApi.loginLMSfun loginLMS(id: String, password: String, completion: (LmsLoginResult) -> Unit)
LMS 아이디와 비밀번호로 로그인합니다. 새 로그인 시도 전에 기존 세션을 제거하며, 로그인 후 사용자 정보가 실제로 조회된 경우에만 성공 처리합니다. 성공 시 유세인트 세션도 자동으로 공유됩니다.
로그인 여부는 외부에서 읽기만 가능한 LmsApi.isLoggined로 확인할 수 있습니다. 기존 호환성을 위해 현재 철자를 유지합니다.
LmsApi.logoutfun logout(completion: () -> Unit)
현재 로그인 상태와 쿠키, 사용자별 성적·채플 최신 조회 조건 캐시를 제거합니다.
LmsApi.getTermsfun getTerms(completion: (LmsTermsResult) -> Unit)
로그인된 사용자의 학기 목록을 가져옵니다.
LmsApi.getTodoListfun getTodoList(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)
fun getTodoList(
term: Term,
loadingState: (Float) -> Unit = {},
postHogDistinctId: String?,
completion: () -> ,
)
과목 기본 정보와 할 일 목록(과제, 동영상 등), 제출 정보를 빠르게 파싱하여 가져옵니다.
postHogDistinctId가 없는 오버로드를 사용하거나 null을 전달하면 분석 데이터를 전송하지 않습니다. 식별자를 전달하면 식별자별 하루 한 번 전송 여부를 20% 비율로 샘플링하며, 선택된 경우 Todo 동기화 통계와 항목 상태 스냅샷을 PostHog로 전송합니다. 이를 의도한 앱에서만 사용하세요.
LmsApi.getSubjectsfun getSubjects(term: Term, loadingState: (Float) -> Unit = {}, completion: (LmsSubjectsResult) -> Unit)
과목 기본 정보, 할 일, 출석, 공지, 제출 및 점수 데이터를 모두 가져옵니다. 여러 API를 내부적으로 호출하므로 getTodoList에 비해 무겁습니다.
LmsApi.getLoginInfofun getLoginInfo(completion: (LmsLoginInfoResult) -> Unit)
로그인한 학생의 이름, 학과, 로그인 ID, 이메일 등의 기본 신원 정보를 가져옵니다.
LmsApi.getCookiesfun getCookies(completion: (LmsCookiesResult) -> Unit)
현재 로그인된 세션의 쿠키 목록을 반환합니다. 외부 서비스와의 연동 시 사용합니다.
// 창업지원단 공지사항 조회
fun loadStartUpNotices(pageNum: Int = 1, completion: (StartUpNoticesResult) -> Unit)
// 장학 공지사항 조회
fun loadScholarships(pageNum: Int = 1, completion: (ScholarshipNoticesResult) -> Unit)
LmsApi.getTimetablesuspend fun getTimetable(): Timetable
suspend : Timetable
개인의 유세인트 시간표를 조회합니다. 학년도와 학기를 생략하면 유세인트가 제공하는 기본 조회 기간을 사용합니다.
LmsApi.getGradeTablesuspend fun getGradeTable(year: String? = null, semester: Semester? = null): GradeTable
fun getGradeTable
지정된 학년도 및 학기의 성적 상세 내역을 조회합니다. 파라미터가 모두 null일 경우 캐싱된 최신 학기 성적을 반환합니다.
LmsApi.getSemesterGradeSummaryTablesuspend fun getSemesterGradeSummaryTable(): SemesterGradeSummaryTable
fun getSemesterGradeSummaryTable(completion: (LmsSemesterGradeSummaryResult) -> Unit)
전체 학기별 신청학점, 취득학점, 평점평균 및 석차 정보가 담긴 성적 요약 데이터를 조회합니다.
LmsApi.getChapelTablesuspend fun getChapelTable(year: String? = null, semester: Semester? = null): ChapelInformation
fun getChapelTable
지정된 학년도 및 학기의 채플 정보(좌석 번호, 주차별 출결, 결석계 신청 현황)를 조회합니다. (계절학기 조회 불가)
LmsApi.getTuitionTablesuspend fun getTuitionTable(): TuitionTable
fun getTuitionTable(completion: (LmsTuitionResult) -> Unit)
학년도/학기별 등록금 고지액, 장학 감면액, 실납부일자 및 납부 금액 등 등록금 납부 이력을 조회합니다.
LmsApi.getScholarshipHistoryTablesuspend fun getScholarshipHistoryTable(): ScholarshipHistoryTable
fun getScholarshipHistoryTable(completion: (LmsScholarshipHistoryResult) -> Unit)
학기별 수혜한 장학금 명칭, 지급 방법, 선발 금액 및 실수혜금액 등의 내역을 조회합니다.
LmsApi.getGraduateTablesuspend fun getGraduateTable(): GraduateTable
fun getGraduateTable(completion: (LmsGraduateTableResult) -> Unit)
졸업사정표 상의 이수구분별 졸업 기준 요건 학점, 본인 취득학점, 차이값 및 판정 결과를 조회합니다.
CyberApi)숭실사이버대학교 전용 API로, LmsApi와는 별도의 독립적인 싱글톤 객체입니다.
CyberApi.loginsuspend fun login(id: String, password: String): Boolean
fun login(id: String, password: String, completion: (CyberLoginResult) -> Unit)
사이버대학교 포털(portal.kcu.ac)에 로그인합니다. 내부적으로 RSA 공개키를 조회하여 비밀번호를 암호화한 뒤 로그인을 수행하며, 성공 시 세션 쿠키를 보관합니다.
로그인 여부는 외부에서 읽기 가능한 CyberApi.isLoggined 프로퍼티로 확인할 수 있습니다.
CyberApi.logoutsuspend fun logout()
fun logout(completion: () -> Unit)
현재 사이버대학교 로그인 세션을 종료하고 로그인 상태를 해제합니다.
CyberApi.getSubjectssuspend fun getSubjects(): List<CyberSubject>
fun getSubjects(completion: (CyberSubjectsResult) -> Unit)
로그인된 사용자의 이번 학기 수강과목 목록(CyberSubject)을 가져옵니다. 과목명, 이수구분, 담당 교수, 학점, 전체 진도율 등이 포함되어 있습니다. (로그인 필수)
CyberApi.getWeeklyLecturessuspend fun getWeeklyLectures(subject: CyberSubject): List<CyberWeek>
fun getWeeklyLectures(subject: CyberSubject, completion: (CyberWeeklyLecturesResult) -> Unit)
특정 수강과목의 주차별 수강일람(CyberWeek) 및 하위 강의 목록(CyberLecture)을 조회합니다. 각 주차별 출석인정기간, 출석 상태, 강의별 학습 시간, 진도율, 완료 여부(isCompleted)를 확인할 수 있습니다. (로그인 필수)
Semester (학기 구분 Enum)FIRST: 1학기 (코드: "090")SUMMER: 여름학기 (코드: "091")SECOND: 2학기 (코드: )Timetable & TimetableCell (시간표)GradeTable & GradeCell (성적 상세)SemesterGradeSummaryTable & SemesterGradeSummaryCell (성적 요약)ChapelInformation (채플 정보)TuitionTable & TuitionCell (등록금)ScholarshipHistoryTable & ScholarshipHistoryCell (장학)GraduateTable & GraduateTableCell (졸업사정표)Term (LMS 학기 정보)id: 학기 고유 IDname: 학기명start_at: 시작 시각end_at: 종료 시각Subject (LMS 수강 과목)TodoList (LMS 할 일)component_type: 항목 타입 (예: assignment, commons)assignment_id: 과제 고유 IDtitle: 제목due_date: 마감 기한Submission (LMS 제출 정보)CyberSubject (사이버대 수강 과목)CyberWeek (사이버대 주차별 수강일람)weekNo: 주차 번호 (예: 1)attendancePeriod: 출석인정기간 (예: "2026.09.01 ~ 2026.09.14")topic: 주차 주제 / 강의명attendanceStatus: 출석 인정 상태 (예: "출석", "결석")lectures: 주차 내 개별 강의 리스트 (List<CyberLecture>)CyberLecture (사이버대 개별 강의)파서와 공개 범위 등 외부 서버가 필요 없는 테스트는 일반 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, , , , , 형식을 지원합니다. 실제 계정 정보는 소스 코드나 커밋에 저장하지 마세요.
사이버대학교(KCU) 계정으로 실제 서버와의 로그인 및 수강과목/수강일람 조회를 검증하려면 아래 환경 변수를 전달합니다:
CYBER_TEST_ID="사이버대학_아이디" \
CYBER_TEST_PASSWORD="사이버대학_비밀번호" \
./gradlew :library:jvmTest \
--tests "io.github.chlwhdtn03.CyberApiFullIntegrationTest" -i
이 저장소의 루트에 있는 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
assembleLmsApiXCFramework는 Debug와 Release를 모두 빌드합니다. 배포용으로는 아래 Release 전용 태스크를 사용합니다. Kotlin/Native 링크 과정의 메모리 부족으로 Gradle 데몬이 종료되는 것을 방지하기 위해 gradle.properties에서 Gradle 힙을 4GB, worker 수를 2로 설정합니다. JVM garbage collector is thrashing 메시지가 나오면 org.gradle.jvmargs와 org.gradle.workers.max의 적용 값을 확인합니다.
./gradlew :library:assembleLmsApiReleaseXCFramework 실행LmsApi.xcframework를 LmsApi.xcframework.zip으로 압축바이너리 프레임워크를 수동으로 내려받거나 직접 로컬 빌드하여 추가할 수도 있습니다.
./gradlew :library:assembleLmsApiReleaseXCFramework
library/build/XCFrameworks/release/LmsApi.xcframework 폴더를 Xcode의 Project Navigator로 드래그하여 드롭합니다.General > Frameworks, Libraries, and Embedded Content 항목에서 LmsApi.xcframework를 등록합니다.현재 소스 설정으로 직접 빌드한 XCFramework에는 실제 기기용 iosArm64 slice와 시뮬레이터용 iosX64 / iosSimulatorArm64 slice가 포함됩니다.
LmsApi 싱글톤과 로그인·조회 콜백 API는 그대로 유지됩니다.parse*, merge*, find*, fetchWebDynproHtml 같은 원본 응답 처리 함수는 내부 구현으로 변경되었습니다. 앱에서는 이 함수들을 직접 호출하지 말고 get* 조회 API를 사용해야 합니다.LmsApi는 프로세스 내에서 로그인 세션, 토큰, 쿠키와 일부 최신 조회 조건을 공유합니다. 새 로그인을 시작하면 이전 사용자의 세션과 캐시를 먼저 비우며, logout도 로그인 상태와 사용자별 캐시를 제거합니다.getTerms, getTodoList, getSubjects 등)는 외부에 콜백 API로 제공됩니다. U-Saint 조회 API는 Kotlin의 suspend 함수와 결과 콜백을 모두 제공합니다.internal 서비스로 분리되었지만 외부 사용법은 변경되지 않았습니다. 내부 구조와 기능 추가 규칙은 내부 구현 가이드를 참고하세요.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}")
}
}
}
ByteArrayimport LmsApi
import PDFKit
extension KotlinByteArray {
func toData() -> Data {
Data((0..<Int(size)).map { index in
UInt8(bitPattern: get(index: Int32(index)))
})
}
}
selectedCourse.loadPlan { bytes, error in
guard let bytes else {
print(error?.localizedDescription ?? "강의계획서 로드 실패")
return
}
let data = bytes.toData()
guard let document = PDFDocument(data: data) else {
print("올바른 PDF 데이터가 아닙니다.")
return
}
DispatchQueue.main.async {
pdfView.document = document
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
}
}
import SwiftUI
import PDFKit
struct NativePDFView: UIViewRepresentable {
let data: Data
func makeUIView(context: Context) -> PDFView {
let view = PDFView()
view.autoScales = true
view.displayMode = .singlePageContinuous
view.displayDirection = .vertical
view.document = PDFDocument(data: data)
return view
}
func updateUIView(_ view: PDFView, context: Context) {
view.document = PDFDocument(data: data)
}
}
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 LmsApi
func loadCyberUniversityLectures() {
Task {
do {
// 1. 사이버대학교 포털 로그인
let loginSuccess = try await CyberApi.shared.login(id: "아이디", password: "비밀번호")
guard loginSuccess.boolValue else {
print("로그인 실패")
return
}
// 2. 이번 학기 수강과목 목록 조회
let subjects = try await CyberApi.shared.getSubjects()
for subject in subjects {
print("과목: \(subject.name) (\(subject.credit)학점) - 진도율: \(subject.progressPercent)%")
}
// 3. 첫 번째 과목의 주차별 수강일람 조회
if let firstSubject = subjects.first {
let weeks = try await CyberApi.shared.getWeeklyLectures(subject: firstSubject)
for week in weeks {
print("[\(week.weekNo)주차] \(week.topic) - 출석상태: \(week.attendanceStatus) (기간: \(week.attendancePeriod))")
for lecture in week.lectures {
print(" - \(lecture.lectureNo)강: \(lecture.statusText), 진도율: \(lecture.progressPercent)%, 학습시간: \(lecture.studyTime)/\(lecture.baseTime), 완료여부: \(lecture.isCompleted)")
}
}
}
// 4. 로그아웃 (세션 정리)
try await CyberApi.shared.logout()
} catch {
print("사이버대학교 정보 조회 실패: \(error.localizedDescription)")
}
}
}
import LmsApi
func loadCyberWithCallback() {
CyberApi.shared.login(id: "아이디", password: "비밀번호") { loginResult in
guard loginResult.success else {
print("로그인 실패: \(loginResult.errorMessage ?? "알 수 없는 오류")")
return
}
CyberApi.shared.getSubjects { subjectsResult in
guard subjectsResult.success else {
print("수강과목 조회 실패: \(subjectsResult.errorMessage ?? "알 수 없는 오류")")
return
}
if let firstSubject = subjectsResult.subjects.first {
CyberApi.shared.getWeeklyLectures(subject: firstSubject) { lecturesResult in
if lecturesResult.success {
print("주차 수: \(lecturesResult.weeks.count)")
}
}
}
}
}
}
import io.github.chlwhdtn03.CyberApi
suspend fun loadCyberUniversityForAndroid() {
try {
// 1. 사이버대학교 포털 로그인
val loginSuccess = CyberApi.login(id = "아이디", password = "비밀번호")
if (!loginSuccess) {
println("로그인 실패")
return
}
// 2. 이번 학기 수강과목 목록 조회
val subjects = CyberApi.getSubjects()
subjects.forEach { subject ->
println("[${subject.name}] 교수: ${subject.professor}, 학점: ${subject.credit}, 진도율: ${subject.progressPercent}%")
}
// 3. 특정 과목의 주차별 수강일람 조회
val firstSubject = subjects.firstOrNull() ?: return
val weeks = CyberApi.getWeeklyLectures(firstSubject)
weeks.forEach { week ->
println("${week.weekNo}주차: ${week.topic} (${week.attendancePeriod}) [${week.attendanceStatus}]")
week.lectures.forEach { lecture ->
println(" - ${lecture.lectureNo}강: ${lecture.statusText}, 진도율: ${lecture.progressPercent}%, 완료여부: ${lecture.isCompleted}")
}
}
// 4. 로그아웃
CyberApi.logout()
} catch (e: Exception) {
println("사이버대학교 조회 중 오류 발생: ${e.message}")
}
}
"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: 이수 여부 판정 (예: "합격", "미필")usedSubjects: 해당 요건에 사용된 과목명 리스트 (과목사용 열을 쉼표 단위로 분리)id: 과목 고유 IDtermId: 학기 IDtermName: 학기명name: 과목명professor: 담당 교수totalStudents: 수강 인원todoList: 과제 및 할 일 목록attendances: 출석 기록 리스트discussions: 공지사항 목록submissions: 과제 제출 정보scoredAssignments: 평가 및 획득한 점수 리스트assignment_id: 과제 고유 IDattachments: 첨부파일 목록attempt: 제출 시도 횟수cached_due_date: 마감 시각late: 지각 여부preview_url: 제출 파일 미리보기 URLsubmitted_at: 제출 시각submission_type: 제출 유형workflow_state: 제출 상태 (예: submitted, graded, unsubmitted)score: 획득 점수name: 과목명category: 이수구분 (예: "전공기초")professor: 담당 교수credit: 학점 (예: "3.0")progressPercent: 전체 진도율 (%)year: 학년도semesterCode: 학기 코드courseCode: 과목 코드deptCode: 학과 코드userNo: 사용자 번호lectureNo: 강 번호 (예: 1)statusText: 학습 상태 텍스트 (예: "학습완료", "미학습")progressPercent: 진도율 (%)studyTime: 학습 시간 (예: "35분 20초")baseTime: 인정 기준 시간 (예: "30분 00초")videoFilePath: 동영상 파일 경로 (nullable)audioFilePath: 오디오 파일 경로 (nullable)isCompleted: 학습 완료 여부 계산 프로퍼티 (progressPercent >= 100)SECOND0900921학기2학기swift package compute-checksum LmsApi.xcframework.zipPackage.swift의 checksum 값 및 URL 경로 업데이트 후 커밋LmsApi.xcframework.zip을 릴리스 에셋으로 업로드Do Not Embed를 선택합니다.getTerms, getTodoList, getSubjects 및 모든 유세인트(U-Saint) API는 반드시 loginLMS 인증이 완료된 후에 정상 호출 가능합니다.object LmsApi가 싱글톤 객체로 변환되어 LmsApi.shared 형태로 접근합니다.LmsApi는 단일 사용자 세션을 공유하므로 같은 프로세스에서 여러 계정의 요청을 동시에 처리하는 용도로 사용할 수 없습니다.CyberApi는 숭실사이버대학교(KCU) 전용 객체로, 숭실대학교 LmsApi와 세션·토큰·쿠키를 전혀 공유하지 않습니다. 사이버대학교 기능 사용 전에는 반드시 CyberApi.login을 호출해야 합니다.object CyberApi 또한 싱글톤 객체로 변환되어 CyberApi.shared 형태로 접근합니다.loadingState 콜백 및 비동기 결과 수신 스레드는 메인(UI) 스레드를 보장하지 않습니다. SwiftUI/UIKit/Compose 등 화면 렌더링에 반영할 경우 메인 디스패처/스레드로의 컨텍스트 스위칭이 필요합니다.Surfaced from shared tags and platforms — no rankings paid for.