diff --git a/Sources/StudentVue/Extensions/CharacterSet+numbers.swift b/Sources/StudentVue/Extensions/CharacterSet+numbers.swift index d155277..73d1b5d 100644 --- a/Sources/StudentVue/Extensions/CharacterSet+numbers.swift +++ b/Sources/StudentVue/Extensions/CharacterSet+numbers.swift @@ -1,6 +1,6 @@ // // CharacterSet+numbers.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/18/23. // @@ -8,7 +8,9 @@ import Foundation extension CharacterSet { - public static let numbers = CharacterSet(charactersIn: "0123456789").inverted + /// A `CharacterSet` disallowing all numerical values. + internal static let numbers = CharacterSet(charactersIn: "0123456789").inverted - public static let numbersextended = CharacterSet(charactersIn: "0123456789/-.").inverted + /// A `CharacterSet` disallowing all numerical values along with some other symbols. + internal static let numbersextended = CharacterSet(charactersIn: "0123456789/-.").inverted } diff --git a/Sources/StudentVue/Extensions/Date+deserialize.swift b/Sources/StudentVue/Extensions/Date+deserialize.swift index c3360ef..2c4abb7 100644 --- a/Sources/StudentVue/Extensions/Date+deserialize.swift +++ b/Sources/StudentVue/Extensions/Date+deserialize.swift @@ -1,6 +1,6 @@ // // Date+deserialize.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/12/23. // @@ -9,13 +9,14 @@ import Foundation import SWXMLHash extension Date: XMLValueDeserialization { - /// Allows for deserialization of dates from an XML element + /// Allows for deserialization of dates from an `XMLElement`. /// - /// - Parameter element: XMLElement where the date is parsed + /// - Parameter element: `XMLElement` where the date is parsed. /// - /// - Throws: `XMLDeserializationError.typeConversionFailed` Unable to convert XMLElement to Date + /// - Throws: `XMLDeserializationError.typeConversionFailed` when unable to convert + /// `XMLElement` to `Date`. /// - /// - Returns: Date converted from XMLElement + /// - Returns: `Date` converted from `XMLElement`. public static func deserialize(_ element: XMLHash.XMLElement) throws -> Date { let date = stringToDate(element.text) @@ -26,13 +27,14 @@ extension Date: XMLValueDeserialization { return validDate } - /// Allows for deserialization of dates from an XML attribute + /// Allows for deserialization of dates from an `XMLAttribute`. /// - /// - Parameter attribute: XMLAttribute where the date is parsed + /// - Parameter attribute: `XMLAttribute` where the date is parsed. /// - /// - Throws: `XMLDeserializationError.typeConversionFailed` Unable to convert XMLAttribute to Date + /// - Throws: `XMLDeserializationError.typeConversionFailed` when unable to convert + /// `XMLAttribute` to `Date`. /// - /// - Returns: Date converted from XMLAttribute + /// - Returns: `Date` converted from `XMLAttribute`. public static func deserialize(_ attribute: XMLAttribute) throws -> Date { let date = stringToDate(attribute.text) diff --git a/Sources/StudentVue/Extensions/Date+stringToDate.swift b/Sources/StudentVue/Extensions/Date+stringToDate.swift index df506a5..842f8d4 100644 --- a/Sources/StudentVue/Extensions/Date+stringToDate.swift +++ b/Sources/StudentVue/Extensions/Date+stringToDate.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// Date+stringToDate.swift +// StudentVue // // Created by TheMoonThatRises on 4/16/23. // @@ -9,14 +9,21 @@ import Foundation import SWXMLHash extension Date { - /// Formats of dates StudentVue returns - private static let decodeDateFormats = ["MM/dd/yy", "MM/dd/yy HH:mm:ss", "MM/dd/yy HH:mm:ss a", "yyyy-MM-dd'T'HH:mm:ss", "HH:mm a"] + /// Formats of dates StudentVue returns. + private static let decodeDateFormats = [ + "MM/dd/yy", + "MM/dd/yy HH:mm:ss", + "MM/dd/yy HH:mm:ss a", + "yyyy-MM-dd'T'HH:mm:ss", + "HH:mm a" + ] - /// Converts String to Date using a defined array of date formats + /// Converts `String` to `Date` using a defined array of date formats. /// - /// - Parameter dateAsString: The date to be converted as a string + /// - Parameter dateAsString: The date to be converted as a string. /// - /// - Returns: Date converted from a string + /// - Returns: `Date` converted from a s`String` or `nil` if the input is + /// not an accepted formatted. public static func stringToDate(_ dateAsString: String) -> Date? { let dateFormatter = DateFormatter() diff --git a/Sources/StudentVue/Extensions/String+PercentEncoding.swift b/Sources/StudentVue/Extensions/String+PercentEncoding.swift index 3739d92..8b7b6ad 100644 --- a/Sources/StudentVue/Extensions/String+PercentEncoding.swift +++ b/Sources/StudentVue/Extensions/String+PercentEncoding.swift @@ -1,6 +1,6 @@ // // String+PercentEncoding.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/9/23. // @@ -8,13 +8,12 @@ import Foundation extension String { - /// Percent encode if possible + /// Percent encode `String.self` for an input of valid characters if possible. /// - /// - Parameters: - /// - withAllowedCharacters: Set of characters to encode + /// - Parameter withAllowedCharacters: Set of characters to encode. /// - /// - Returns: String percent encoded with character set - func percentEncoding(withAllowedCharacters: CharacterSet) -> String { + /// - Returns: String percent encoded with character set. + internal func percentEncoding(withAllowedCharacters: CharacterSet) -> String { self.addingPercentEncoding(withAllowedCharacters: withAllowedCharacters) ?? self } } diff --git a/Sources/StudentVue/Extensions/String+replacing.swift b/Sources/StudentVue/Extensions/String+replacing.swift index 53d985a..75f39f5 100644 --- a/Sources/StudentVue/Extensions/String+replacing.swift +++ b/Sources/StudentVue/Extensions/String+replacing.swift @@ -1,6 +1,6 @@ // // String+replacing.swift -// +// StudentVue // // Created by TheMoonThatRises on 8/9/23. // @@ -8,25 +8,26 @@ import Foundation extension String { - /// Replace substring with another substring in the current string + /// Replace substring with another substring in the current string. /// /// - Parameters: - /// - from: Substring of characters to replace - /// - with: Substring of replacing characters + /// - from: Substring of characters to replace. + /// - with: Substring of replacing characters. /// - /// - Returns: String with substring replaced - public func replacing(_ from: String, with: String) -> Self { + /// - Returns: String with substring replaced. + internal func replacing(_ from: String, with: String) -> Self { self.replacingOccurrences(of: from, with: with) } - /// Matches current string with a regex and returns all matches - /// https://stackoverflow.com/a/34460111 + /// Matches current string with a regex and returns all matches. /// - /// - Parameters: - /// - regex: Regex to match the current string + /// This function is taken from + /// [https://stackoverflow.com/a/34460111](https://stackoverflow.com/a/34460111). + /// + /// - Parameter regex: Regex to match the current string. /// - /// - Returns: An array of matched strings - public func matches(_ regex: String) -> [Self] { + /// - Returns: An array of matched strings. + internal func matches(_ regex: String) -> [Self] { do { let regex = try NSRegularExpression(pattern: regex) let results = regex.matches(in: self, diff --git a/Sources/StudentVue/Extensions/String+unescape.swift b/Sources/StudentVue/Extensions/String+unescape.swift index b50443d..082be63 100644 --- a/Sources/StudentVue/Extensions/String+unescape.swift +++ b/Sources/StudentVue/Extensions/String+unescape.swift @@ -1,6 +1,6 @@ // // String+unescape.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/11/23. // @@ -8,8 +8,8 @@ import Foundation extension String { - /// Unescapes left and right angle brackets - var unescape: String { + /// Unescapes left and right angle brackets. + internal var unescape: String { let characters = [ // "&": "&", "<": "<", @@ -17,10 +17,13 @@ extension String { // """: "\\\"", // "'": "'" ] + var str = self + for (escaped, unescaped) in characters { str = str.replacingOccurrences(of: escaped, with: unescaped, options: .literal, range: nil) } + return str } } diff --git a/Sources/StudentVue/Extensions/XMLHash+parse.swift b/Sources/StudentVue/Extensions/XMLHash+parse.swift index 6a30700..6b48b77 100644 --- a/Sources/StudentVue/Extensions/XMLHash+parse.swift +++ b/Sources/StudentVue/Extensions/XMLHash+parse.swift @@ -1,6 +1,6 @@ // // XMLHash+parse.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/14/23. // @@ -9,16 +9,27 @@ import Foundation import SWXMLHash public extension XMLHash { - static fileprivate let errorAttributes = ["ERROR_MESSAGE", "errorMessage"] + /// Attributes SOAP messages include when an error has occured. + static private let errorAttributes = ["ERROR_MESSAGE", "errorMessage"] - /// Wrapper of XMLHash parse function + /// Wrapper of XMLHash parse function. /// - /// - Parameter soapString: The SOAP XML to parse + /// This function loops through all of the first layer of children in the requests looking + /// for an attribute that is defined in `errorAttributes`. This attribute is then read and + /// throws an error. ``StudentVueApi/StudentVueErrors/invalidCredentials`` is the only specific + /// error message thrown. Other messages are thrown through a generic + /// ``StudentVueApi/StudentVueErrors/soapError(_:)`` with the error message + /// as the string value. /// - /// - Throws: `StudentVueErrors.soapError` An error was returned by the StudentVue API + /// - Note: This function may be ineffecient as it loops through all of the first layer + /// of the returned XML. /// - /// - Returns: An XMLIndexer with only the body of the SOAP response - class func parse(soapString: String) throws -> XMLIndexer { + /// - Parameter soapString: The SOAP XML to parse. + /// + /// - Throws: ``StudentVueErrors/soapError`` when an error was returned by StudentVue's API. + /// + /// - Returns: An XMLIndexer with only the body of the SOAP response. + static internal func parse(soapString: String) throws -> XMLIndexer { let request = parse(soapString)["soap:Envelope"]["soap:Body"]["ProcessWebServiceRequestMultiWebResponse"]["ProcessWebServiceRequestMultiWebResult"] do { diff --git a/Sources/StudentVue/SOAPApi/Models/Attendance.swift b/Sources/StudentVue/SOAPApi/Models/Attendance.swift index d09496f..f4b800d 100644 --- a/Sources/StudentVue/SOAPApi/Models/Attendance.swift +++ b/Sources/StudentVue/SOAPApi/Models/Attendance.swift @@ -1,6 +1,6 @@ // // Attendance.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -10,15 +10,34 @@ import SWXMLHash extension StudentVueApi { public struct AbsencePeriod: XMLObjectDeserialization { + /// The period the student was absent. public var period: Int + + /// Unknown. public var name: String + + /// The reason the student was absent. public var reason: String + + /// The course the student was absent from. public var course: String + + /// The teacher of the class the student was absent from. public var teacher: String + + /// The email of the teacher. public var teacherEmail: String + + /// Unknown. public var iconName: String + + /// The name of the school. public var schoolName: String + + /// The GU of the teacher of the class. public var teacherGU: String + + /// The GU year the absence occured. public var orgYearGU: String public static func deserialize(_ element: XMLIndexer) throws -> AbsencePeriod { @@ -36,12 +55,25 @@ extension StudentVueApi { } public struct Absence: XMLObjectDeserialization { + /// The date of the absence. public var date: Date + + /// The reason of the absence. public var reason: String + + /// The note of the absence. public var note: String + + /// Unkown. public var dailyIconName: String + + /// Unknown. public var codeAllDayReasonType: String + + /// Unknown. public var codeAllDayDescription: String + + /// List of periods the student was absent from. public var absencePeriods: [AbsencePeriod] public static func deserialize(_ element: XMLIndexer) throws -> Absence { @@ -66,7 +98,10 @@ extension StudentVueApi { } public struct ConcurrentSchoolsList: XMLObjectDeserialization { + /// Name of the school the student is going to in addition to their primary school. public var concurrentSchoolName: String + + /// GU of the year for the concurrent school. public var concurrentOrgYearGU: String public static func deserialize(_ element: XMLIndexer) throws -> ConcurrentSchoolsList { @@ -76,17 +111,40 @@ extension StudentVueApi { } public struct Attendance: XMLObjectDeserialization { + /// Unkown. public var type: String + + /// Unknown. public var startPeriod: Int + + /// Unkown. public var endPeriod: Int + + /// Unkown. public var periodCount: Int + + /// Name of the school. public var schoolName: String + + /// List of all absences. public var absences: [Absence] + + /// List of excused absences. public var totalExcused: [AttendancePeriodTotal] + + /// List of tardies. public var totalTardies: [AttendancePeriodTotal] + + /// List of unexcused absences. public var totalUnexcused: [AttendancePeriodTotal] + + /// List of excused absences due to an activity. public var totalActivities: [AttendancePeriodTotal] + + /// List of unexcused tardies. public var totalUnexcusedTardies: [AttendancePeriodTotal] + + /// List of schools the student is concurrently attending. public var concurrentSchoolsLists: [ConcurrentSchoolsList] public static func deserialize(_ element: XMLIndexer) throws -> Attendance { diff --git a/Sources/StudentVue/SOAPApi/Models/ClassSchedule.swift b/Sources/StudentVue/SOAPApi/Models/ClassSchedule.swift index 91ca77f..28d196a 100644 --- a/Sources/StudentVue/SOAPApi/Models/ClassSchedule.swift +++ b/Sources/StudentVue/SOAPApi/Models/ClassSchedule.swift @@ -1,6 +1,6 @@ // // ClassSchedule.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -10,21 +10,52 @@ import SWXMLHash extension StudentVueApi { public struct ClassScheduleInfo: XMLObjectDeserialization { + /// Class period. public var period: String + + /// Name of the class. public var className: String + + /// URL for the class provided by the teacher (or when linked to Google Classroom). public var classURL: String + + /// The start time of the class. public var startTime: Date + + /// The end time of the class. public var endTime: Date + + /// Name of the teacher. public var teacherName: String + + /// Unkown. public var teacherURL: String + + /// The room name in the school. public var roomName: String + + /// The email of the teacher. public var teacherEmail: String + + /// Unkown. public var emailSubject: String + + /// The GU of the teacher. public var teacherGU: String + + /// Unkown. public var startDate: Date + + /// Unkown. public var endDate: Date + + /// Unkown. public var sectionGU: String + + /// Unkown. public var hideClassStartEndTime: Bool + + /// Unkown. public var attendanceCode: String? // TODO: Find data type/structure public static func deserialize(_ element: XMLIndexer) throws -> ClassScheduleInfo { @@ -47,8 +78,13 @@ extension StudentVueApi { } public struct SchoolScheduleInfo: XMLObjectDeserialization { + /// Name of the school. public var schoolName: String + + /// Name of the bell schedule. public var bellScheduleName: String + + /// List of classes the student is taking. public var classes: [ClassScheduleInfo] public static func deserialize(_ element: XMLIndexer) throws -> SchoolScheduleInfo { @@ -59,7 +95,10 @@ extension StudentVueApi { } public struct TodayScheduleInfo: XMLObjectDeserialization { + /// Current date. public var date: Date + + /// List of classes the student is taking today. public var schoolInfos: [SchoolScheduleInfo] public static func deserialize(_ element: XMLIndexer) throws -> TodayScheduleInfo { @@ -73,13 +112,28 @@ extension StudentVueApi { } public struct ClassListSchedule: XMLObjectDeserialization { + /// Class period. public var period: Int + + /// Title of the class. public var courseTitle: String + + /// Room name in the school. public var roomName: String + + /// The teacher of the class. public var teacher: String + + /// The email of the teacher. public var teacherEmail: String + + /// Unkown. public var sectionGU: String + + /// The GU of the teacher. public var teacherGU: String + + /// Unkown. public var additionalStaffInformationXMLs: [AdditionalStaffInformationXML]? public static func deserialize(_ element: XMLIndexer) throws -> ClassListSchedule { @@ -94,6 +148,7 @@ extension StudentVueApi { } public struct TermDefCodesSchedule: XMLObjectDeserialization { + /// The term name (e.x. `S1`, `S2`, `YR`). public var termDefName: String public static func deserialize(_ element: XMLIndexer) throws -> TermDefCodesSchedule { @@ -102,12 +157,25 @@ extension StudentVueApi { } public struct TermListSchedule: XMLObjectDeserialization { + /// Current term. public var termIndex: Int + + /// ``termIndex`` + 1. public var termCode: Int + + /// Name of the current term. public var termName: String + + /// Begin date of the term. public var beginDate: Date + + /// End date of the term. public var endDate: Date + + /// GU code for the school's year term. public var schoolYearTermCodeGU: String + + /// List of term definitions. public var termDefCodes: [TermDefCodesSchedule] public static func deserialize(_ element: XMLIndexer) throws -> TermListSchedule { @@ -122,14 +190,31 @@ extension StudentVueApi { } public struct ClassListing: XMLObjectDeserialization { + /// The email of the teacher. public var teacherEmail: String + + /// Unkown. public var excludePVUE: Bool + + /// Name of the teacher. public var teacher: String + + /// Period of the class. public var period: Int + + /// Name of the class. public var courseTitle: String + + /// GU of the teacher. public var teacherStaffGU: String + + /// Unkown. public var sectionGU: String + + /// Room name in the school. public var roomName: String + + /// Unkown. public var additionalStaffInformationXMLs: [AdditionalStaffInformationXML]? public static func deserialize(_ element: XMLIndexer) throws -> ClassListing { @@ -145,11 +230,22 @@ extension StudentVueApi { } public struct ConcurrentSchoolStudentClassSchedule: XMLObjectDeserialization { + /// Term index name. public var conSchTermIndexName: String + + /// Unkown. public var conSchOrgYearGU: String + + /// Term index. public var conSchTermIndex: Int + + /// Name of the concurrent school. public var schoolName: String + + /// Error message. public var conSchErrorMessage: String + + /// List of classes the student is taking at the concurrent school. public var conSchClassLists: [ClassListing] public static func deserialize(_ element: XMLIndexer) throws -> ConcurrentSchoolStudentClassSchedule { @@ -163,13 +259,28 @@ extension StudentVueApi { } public struct ClassSchedule: XMLObjectDeserialization { + /// Current term index. public var termIndex: Int + + /// Current term index name. public var termIndexName: String + + /// Error message. public var errorMessage: String - public var includeAdditionaWhenEmailingTeachers: Bool + + /// Unkown. + public var includeAdditionalWhenEmailingTeachers: Bool + + /// Today's schedule. `nil` when today is not a school day. public var todayScheduleInfo: TodayScheduleInfo? + + /// List of classes taken by the student. public var classLists: [ClassListSchedule] + + /// List of terms. public var termLists: [TermListSchedule] + + /// Schedule of the student if they are attending multiple schools. public var concurrentSchoolStudentClassSchedules: [ConcurrentSchoolStudentClassSchedule] public static func deserialize(_ element: XMLIndexer) throws -> ClassSchedule { @@ -178,7 +289,7 @@ extension StudentVueApi { return ClassSchedule(termIndex: try schedule.value(ofAttribute: "TermIndex"), termIndexName: try schedule.value(ofAttribute: "TermIndexName"), errorMessage: try schedule.value(ofAttribute: "ErrorMessage"), - includeAdditionaWhenEmailingTeachers: try schedule.value(ofAttribute: "IncludeAdditionalStaffWhenEmailingTeachers"), + includeAdditionalWhenEmailingTeachers: try schedule.value(ofAttribute: "IncludeAdditionalStaffWhenEmailingTeachers"), todayScheduleInfo: try? schedule["TodayScheduleInfoData"].value(), classLists: try schedule["ClassLists"]["ClassListing"].value(), termLists: try schedule["TermLists"]["TermListing"].value(), diff --git a/Sources/StudentVue/SOAPApi/Models/Districts.swift b/Sources/StudentVue/SOAPApi/Models/Districts.swift index 723bac9..c696e78 100644 --- a/Sources/StudentVue/SOAPApi/Models/Districts.swift +++ b/Sources/StudentVue/SOAPApi/Models/Districts.swift @@ -1,6 +1,6 @@ // // Districts.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/11/23. // @@ -10,9 +10,16 @@ import SWXMLHash extension StudentVueApi { public struct DistrictInfo: XMLObjectDeserialization { + /// ID of the district. public var districtID: String + + /// Name of the district. public var districtName: String + + /// Address of the district. public var districtAddress: String + + /// StudentVue URL address. public var districtURL: URL public static func deserialize(_ element: XMLIndexer) throws -> DistrictInfo { @@ -24,6 +31,7 @@ extension StudentVueApi { } public struct Districts: XMLObjectDeserialization { + /// List of districts. public var districts: [DistrictInfo] public static func deserialize(_ element: XMLIndexer) throws -> Districts { diff --git a/Sources/StudentVue/SOAPApi/Models/GradeBook.swift b/Sources/StudentVue/SOAPApi/Models/GradeBook.swift index 0c3ec46..bc9e763 100644 --- a/Sources/StudentVue/SOAPApi/Models/GradeBook.swift +++ b/Sources/StudentVue/SOAPApi/Models/GradeBook.swift @@ -1,6 +1,6 @@ // // GradeBook.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/12/23. // @@ -10,9 +10,16 @@ import SWXMLHash extension StudentVueApi { public struct GradingPeriod: XMLObjectDeserialization { + /// Index of the grading period. public var index: Int? + + /// Name of the grading period. public var gradePeriodName: String + + /// Start date of the grading period. public var startDate: Date + + /// End date of the grading period. public var endDate: Date public static func deserialize(_ element: XMLIndexer) throws -> GradingPeriod { @@ -23,19 +30,42 @@ extension StudentVueApi { } } + /// Resource teacher provides with an assignment. public struct GradeBookResource: XMLObjectDeserialization { + /// Class ID. public var classID: String + + /// Type of resource. public var fileType: String? + + /// Gradebook ID. public var gradebookID: String + + /// Date resource was created. public var resourceDate: Date + + /// Description of the resource. public var resourceDescription: String + + /// Resource ID. public var resourceID: String + + /// Name of the resource. public var resourceName: String + + /// Unkown. public var sequence: String + + /// Teacher ID. public var teacherID: String + + /// Unkown. public var type: String // TODO: Find other data types + /// URL of the resource if provided. public var url: URL? + + /// Unkown. public var serverFileName: String public static func deserialize(_ element: XMLIndexer) throws -> GradeBookResource { @@ -54,22 +84,54 @@ extension StudentVueApi { } } + /// Assignment assigned by the teacher entered into the gradebook. public struct GradeBookAssignment: XMLObjectDeserialization { + /// Assignment ID. public var gradeBookID: String + + /// Name of the assignment. public var measure: String + + /// Assignment type (e.x. participation, test). public var type: String + + /// Assignment date. public var date: Date + + /// Due date of the assignment. public var dueDate: Date + + /// Type of score. public var scoreType: String + + /// Points recieved. public var points: String + + /// Total seconds since the assignment was posted. public var totalSecondsSincePost: Double + + /// Unkown. public var notes: String + + /// Student ID. public var teacherID: String + + /// Teacher ID. public var studentID: String + + /// Description of the assignment. public var measureDescription: String + + /// If the assignment has a Drop Box. public var hasDropBox: Bool + + /// Unkown. public var dropStartDate: Date + + /// Unkown. public var dropEndDate: Date + + /// A list of resources the teacher provides with the assignment. public var resources: [GradeBookResource] public static func deserialize(_ element: XMLIndexer) throws -> GradeBookAssignment { @@ -92,10 +154,18 @@ extension StudentVueApi { } } + /// Grading period assignments and grade. public struct Grade: XMLObjectDeserialization { + /// Name of the grading period. public var gradePeriodName: String + + /// Letter grade when calculated. public var calculatedGrade: String + + /// Raw grade recieved. public var calculatedGradeRaw: Float + + /// List of assignments in the grading period. public var assignments: [GradeBookAssignment] public static func deserialize(_ element: XMLIndexer) throws -> Grade { @@ -106,15 +176,33 @@ extension StudentVueApi { } } + /// Course taken by the student in the gradebook. public struct Course: XMLObjectDeserialization { + /// Unkown. public var usesRichContent: Bool + + /// Period of the class. public var period: Int + + /// Name of the class. public var name: String + + /// Room name in the school. public var room: String + + /// Name of the teacher. public var teacher: String + + /// Teacher email for the course. public var teacherEmail: String + + /// GU of the teacher. public var teacherGU: String + + /// Cut-off bar for when the grade should be highlighted(?). public var highlightPercentageCutOffForProgressBar: Int + + /// List of grades throughout grading periods in the course. public var grades: [Grade] public static func deserialize(_ element: XMLIndexer) throws -> Course { @@ -130,9 +218,15 @@ extension StudentVueApi { } } + /// Gradebook for the student. public struct GradeBook: XMLObjectDeserialization { + /// List of available grading periods. public var gradingPeriods: [GradingPeriod] + + /// Current grading period. public var cuarrentGradingPeriod: GradingPeriod + + /// List of courses taken by the student with their grades. public var courses: [Course] public static func deserialize(_ element: XMLIndexer) throws -> GradeBook { diff --git a/Sources/StudentVue/SOAPApi/Models/MessageAttachment.swift b/Sources/StudentVue/SOAPApi/Models/MessageAttachment.swift index d00a8b4..c340cbd 100644 --- a/Sources/StudentVue/SOAPApi/Models/MessageAttachment.swift +++ b/Sources/StudentVue/SOAPApi/Models/MessageAttachment.swift @@ -1,6 +1,6 @@ // // MessageAttachment.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/14/23. // @@ -9,8 +9,12 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Undocumented API. public struct MessageAttachment: XMLObjectDeserialization { + /// Name of the document. public var documentName: String + + /// The document in the form of a Base64 string. public var base64Code: String public static func deserialize(_ element: XMLIndexer) throws -> MessageAttachment { diff --git a/Sources/StudentVue/SOAPApi/Models/PXPMessages.swift b/Sources/StudentVue/SOAPApi/Models/PXPMessages.swift index 14ed221..c073f43 100644 --- a/Sources/StudentVue/SOAPApi/Models/PXPMessages.swift +++ b/Sources/StudentVue/SOAPApi/Models/PXPMessages.swift @@ -1,6 +1,6 @@ // // PXPMessages.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // diff --git a/Sources/StudentVue/SOAPApi/Models/ReportCards.swift b/Sources/StudentVue/SOAPApi/Models/ReportCards.swift index a1fe22b..e47a3eb 100644 --- a/Sources/StudentVue/SOAPApi/Models/ReportCards.swift +++ b/Sources/StudentVue/SOAPApi/Models/ReportCards.swift @@ -1,6 +1,6 @@ // // ReportCards.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -9,6 +9,7 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Undocumented API. public struct RCReportingPeriod: XMLObjectDeserialization { public var reportingPeriodGU: String public var reportingPeriodName: String @@ -25,6 +26,7 @@ extension StudentVueApi { } } + /// Undocumented API. public struct ReportCards: XMLObjectDeserialization { public var rcReportingPeriods: [RCReportingPeriod] @@ -33,6 +35,7 @@ extension StudentVueApi { } } + /// Undocumented API. public struct ReportCard: XMLObjectDeserialization { public var documentGU: String public var fileName: String diff --git a/Sources/StudentVue/SOAPApi/Models/SchoolInfo.swift b/Sources/StudentVue/SOAPApi/Models/SchoolInfo.swift index 4e5dbbe..80a5c11 100644 --- a/Sources/StudentVue/SOAPApi/Models/SchoolInfo.swift +++ b/Sources/StudentVue/SOAPApi/Models/SchoolInfo.swift @@ -1,6 +1,6 @@ // // SchoolInfo.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -9,12 +9,24 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Staff information. public struct StaffInfo: XMLObjectDeserialization { + /// Name of the staff member. public var name: String + + /// Email of the staff member. public var email: String + + /// Position of the staff member. public var title: String + + /// Phone number of the staff member. public var phone: String + + /// Extension for the phone number. public var extn: String + + /// Staff GU. public var staffGU: String public static func deserialize(_ element: XMLIndexer) throws -> StaffInfo { @@ -27,19 +39,45 @@ extension StudentVueApi { } } + /// Full information about the school. public struct SchoolInfo: XMLObjectDeserialization { + /// Name of the school. public var school: String + + /// Name of the principal. public var principal: String + + /// Address of the school. public var address: String + + /// Second address of the school. public var address2: String + + /// City location of the school. public var city: String + + /// State the school is located in. public var state: String + + /// School zip code. public var zip: String + + /// School phone number. public var phone: String + + /// School Fax. public var phone2: String + + /// School homepage. public var homepage: URL? + + /// Email of the school's principal. public var principalEmail: String + + /// GU of the principal. public var principalGU: String + + /// List of staff members at the school. public var staffList: [StaffInfo] public static func deserialize(_ element: XMLIndexer) throws -> SchoolInfo { diff --git a/Sources/StudentVue/SOAPApi/Models/StudentCalendar.swift b/Sources/StudentVue/SOAPApi/Models/StudentCalendar.swift index e9cd4bd..a0e9d4a 100644 --- a/Sources/StudentVue/SOAPApi/Models/StudentCalendar.swift +++ b/Sources/StudentVue/SOAPApi/Models/StudentCalendar.swift @@ -1,6 +1,6 @@ // // StudentCalendar.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -9,16 +9,36 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Calendar event. public struct CalendarEventList: XMLObjectDeserialization { + /// Calendar event date. public var date: Date + + /// Name of the event. public var title: String + + /// Unkown. public var icon: String? + + /// Unkown. public var agu: String? + + /// Type of event. public var dayType: String + + /// Event start time. public var startTime: String + + /// Unkown. public var link: String? + + /// Unkown. public var dgu: String? + + /// Unkown. public var viewType: Int? + + /// Unkown. public var addLinkData: String? public static func deserialize(_ element: XMLIndexer) throws -> CalendarEventList { @@ -35,11 +55,21 @@ extension StudentVueApi { } } + /// School calendar which contains assignment due dates, holidays, and school breaks. public struct StudentCalendar: XMLObjectDeserialization { + /// Start date of the school. public var schoolStartDate: Date + + /// End date of the school. public var schoolEndDate: Date + + /// Starting month of the school. public var monthStartDate: Date + + /// Ending month of the school. public var monthEndDate: Date + + /// List of events. public var eventLists: [CalendarEventList] public static func deserialize(_ element: XMLIndexer) throws -> StudentCalendar { diff --git a/Sources/StudentVue/SOAPApi/Models/StudentDocuments.swift b/Sources/StudentVue/SOAPApi/Models/StudentDocuments.swift index 6f954ee..1f29da2 100644 --- a/Sources/StudentVue/SOAPApi/Models/StudentDocuments.swift +++ b/Sources/StudentVue/SOAPApi/Models/StudentDocuments.swift @@ -1,6 +1,6 @@ // // StudentDocuments.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/14/23. // @@ -9,12 +9,24 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Student document metadata. public struct StudentDocumentData: XMLObjectDeserialization { + /// GU of the document. public var documentGU: String + + /// File name of the document. public var documentFileName: String + + /// Date the document was uploaded. public var documentDate: Date + + /// Type of document. public var documentType: String + + /// GU of the student. public var studentGU: String + + /// Comment with the document. public var documentComment: String public static func deserialize(_ element: XMLIndexer) throws -> StudentDocumentData { @@ -28,8 +40,13 @@ extension StudentVueApi { } public struct StudentDocuments: XMLObjectDeserialization { + /// GU of the student. public var studentGU: String + + /// Unkown. public var studentSSY: String + + /// List of documents the student has. Does not contain the actual document public var studentDocumentDatas: [StudentDocumentData] public static func deserialize(_ element: XMLIndexer) throws -> StudentDocuments { @@ -41,13 +58,27 @@ extension StudentVueApi { } } + /// Document information. public struct DocumentData: XMLObjectDeserialization { + /// GU of the document. public var documentGU: String + + /// GU of the student. public var studentGU: String + + /// File name of the document. public var fileName: String + + /// Category of the document. public var category: String + + /// Notes about the document. public var notes: String + + /// Type of the document. public var docType: String + + /// The document as a Base64 string. public var base64Code: String public static func deserialize(_ element: XMLIndexer) throws -> DocumentData { @@ -62,7 +93,10 @@ extension StudentVueApi { } public struct StudentAttachedDocumentData: XMLObjectDeserialization { + /// Unkown. public var documentCategoryLookups: [String]? // TODO: Find data type/structure + + /// List of documents. public var documentDatas: [DocumentData] public static func deserialize(_ element: XMLIndexer) throws -> StudentAttachedDocumentData { diff --git a/Sources/StudentVue/SOAPApi/Models/StudentHealthInfo.swift b/Sources/StudentVue/SOAPApi/Models/StudentHealthInfo.swift index 2b410d7..bfa48c9 100644 --- a/Sources/StudentVue/SOAPApi/Models/StudentHealthInfo.swift +++ b/Sources/StudentVue/SOAPApi/Models/StudentHealthInfo.swift @@ -1,6 +1,6 @@ // // StudentHealthInfo.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/15/23. // @@ -9,6 +9,7 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Undocumented API. public struct HealthVisitListing: XMLObjectDeserialization { // TODO: Find data structure @@ -17,6 +18,7 @@ extension StudentVueApi { } } + /// Undocumented API. public struct HealthConditionListing: XMLObjectDeserialization { // TODO: Find data structure @@ -25,12 +27,25 @@ extension StudentVueApi { } } + /// Immunization records of the student submitted by their parent when registering + /// their student through StudentVue. public struct HealthImmunizationListing: XMLObjectDeserialization { + /// GU of the immunization item. public var accessGU: String + + /// If the student is compliant with the immunization. public var compliant: Bool + + /// If the student needs to take the immunization. public var compliantMessage: String + + /// Name of the immunization shot. public var name: String + + /// Number of required doses. public var numReqDoses: Int + + /// Dates the student was immunized. public var immunizationDates: [Date] public static func deserialize(_ element: XMLIndexer) throws -> HealthImmunizationListing { @@ -44,8 +59,13 @@ extension StudentVueApi { } public struct StudentHealthInfo: XMLObjectDeserialization { + /// List of student health visits. public var healtVisitListings: [HealthVisitListing] + + /// List of student health conditions. public var healthConditionListings: [HealthConditionListing] + + /// List of student immunization records. public var healthImmunizationListing: [HealthImmunizationListing] public static func deserialize(_ element: XMLIndexer) throws -> StudentHealthInfo { diff --git a/Sources/StudentVue/SOAPApi/Models/StudentInfo.swift b/Sources/StudentVue/SOAPApi/Models/StudentInfo.swift index b2ac1ab..09f9ebc 100644 --- a/Sources/StudentVue/SOAPApi/Models/StudentInfo.swift +++ b/Sources/StudentVue/SOAPApi/Models/StudentInfo.swift @@ -1,6 +1,6 @@ // // StudentInfo.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/12/23. // @@ -9,12 +9,25 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Emergency contacts provided by the parent of the student when registering their student + /// through StudentVue. public struct EmergencyContact: XMLObjectDeserialization { + /// Emergency contact name. public var name: String + + /// Relationship between the emergency contact and the student. public var relationship: String + + /// Home phone number. public var homePhone: String + + /// Work phone number. public var workPhone: String + + /// Other phone number. public var otherPhone: String + + /// Mobile phone number. public var mobilePhone: String public static func deserialize(_ element: XMLIndexer) throws -> EmergencyContact { @@ -27,10 +40,19 @@ extension StudentVueApi { } } + /// The physican of the student submitted by the parent when registering their student + /// through StudentVue. public struct PhysicianInfo: XMLObjectDeserialization { + /// Physician name. public var name: String + + /// Physician working hospital. public var hospital: String + + /// Physician's phone number. public var phone: String + + /// Extension for the phone number. public var extn: String public static func deserialize(_ element: XMLIndexer) throws -> PhysicianInfo { @@ -41,10 +63,19 @@ extension StudentVueApi { } } + /// The dentist of the student submitted by the parent when registering their student + /// through StudentVue. public struct DentistInfo: XMLObjectDeserialization { + /// Dentist name. public var name: String + + /// Dentist's office. public var office: String + + /// Dentist's phone number. public var phone: String + + /// Extension for the phone number. public var extn: String public static func deserialize(_ element: XMLIndexer) throws -> DentistInfo { @@ -55,12 +86,24 @@ extension StudentVueApi { } } + /// Items defined for the student automatically. public struct UserDefinedItem: XMLObjectDeserialization { + /// Name of the item. public var itemLabel: String + + /// Type of the item. public var itemType: String + + /// Unkown. public var sourceObject: String + + /// Unkown. public var sourceElement: String + + /// Unkown. public var vcid: String + + /// The value of the item. public var value: String public static func deserialize(_ element: XMLIndexer) throws -> UserDefinedItem { @@ -73,31 +116,82 @@ extension StudentVueApi { } } + /// Information about the student. Some information is submitted by the parent + /// while other information may be automatically assigned. public struct StudentInfo: XMLObjectDeserialization { + /// Unkown. public var lockerInfoRecords: String? // TODO: Find data type + + /// Formatted full name of the student. public var formattedName: String + + /// Student school ID. public var permID: String + + /// Student gender. public var gender: String + + /// Current grade of the student. public var grade: String + + /// Home address of the student. public var address: String + + /// Unkown. public var lastNameGoesBy: String? + + /// Student nickname. public var nickname: String? + + /// Student birth date. public var birthDate: Date + + /// Student school email address. public var email: String + + /// Phone number provided by the parent. public var phone: String + + /// Spoken language at home. public var homeLanguage: String + + /// Name of the school currently attended by the student. public var currentSchool: String + + /// Unkown. public var track: String? // TODO: Find data type + + /// Student's home room teacher. public var homeRoomTeacher: String + + /// Email address of the home room teacher. public var homeRoomTeacherEmail: String + + /// GU of the home room teacher. public var homeRoomTeacherGU: String + + /// Unkown. public var orgYearGU: String + + /// Home room name in the school. public var homeRoom: String + + /// Name of the student's counselor. public var counselorName: String + + /// Photo of the student. Base64 string. public var photo: String? + + /// List of emergency contacts. public var emergencyContacts: [EmergencyContact] + + /// Physican information for the student. public var physicianInfo: PhysicianInfo + + /// Dentist information for the student. public var dentistInfo: DentistInfo + + /// Items automatically created for the student. Usually useful information. public var userDefinedItems: [UserDefinedItem] public static func deserialize(_ element: XMLIndexer) throws -> StudentInfo { diff --git a/Sources/StudentVue/SOAPApi/Models/StudetHWNotes.swift b/Sources/StudentVue/SOAPApi/Models/StudetHWNotes.swift index 5054240..3919cb6 100644 --- a/Sources/StudentVue/SOAPApi/Models/StudetHWNotes.swift +++ b/Sources/StudentVue/SOAPApi/Models/StudetHWNotes.swift @@ -1,6 +1,6 @@ // // StudentHWNotes.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/13/23. // @@ -9,10 +9,18 @@ import Foundation import SWXMLHash extension StudentVueApi { + /// Undocumented API. public struct StudentHWNotes: XMLObjectDeserialization { + /// GU of the student. public var studentGU: String + + /// Student information system number. public var sisNumber: String + + /// Unkown. public var studentSSY: String + + /// Unkown. public var gBHomeWorkNotesRecords: [String]? // TODO: Find data type/structure public static func deserialize(_ element: XMLIndexer) throws -> StudentHWNotes { diff --git a/Sources/StudentVue/SOAPApi/StudentVueApi.swift b/Sources/StudentVue/SOAPApi/StudentVueApi.swift index 1b6463f..d532226 100644 --- a/Sources/StudentVue/SOAPApi/StudentVueApi.swift +++ b/Sources/StudentVue/SOAPApi/StudentVueApi.swift @@ -1,6 +1,6 @@ // // StudentVueApi.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/3/23. // @@ -8,62 +8,46 @@ import Foundation import SWXMLHash +/// Class for interacting with StudentVue's official SOAP API. +/// +/// ``StudentVueApi`` recreates API requests submitted by the official StudentVue app, using +/// a combination of community compiled requests and responses and through transparent proxies. +/// +/// New API endpoints and methods can be found by using MITMProxy and the official StudentVue app +/// installed on a Mac. +/// +/// A new instance can be created with ``StudentVueApi/init(domain:username:password:)``, but the +/// prefered method is by using ``StudentVue/StudentVue`` and initializing with +/// ``StudentVue/StudentVue/init(domain:username:password:)`` and accessing it through its +/// ``StudentVue/StudentVue/api``. public class StudentVueApi { - /// Endpoints that StudentVue uses for it's API. `HDInfoCommunication` is only used within `support.edupoint.com` - public enum Endpoints: String, Equatable { - case pxpCommunication = "PXPCommunication" - case hdInfoCommunication = "HDInfoCommunication" - } - - /// SOAP methods that StudentVue uses - public enum Methods: String { - case getMatchingDistrictList = "GetMatchingDistrictList" - case getPXPMessages = "GetPXPMessages" - case studentCalendar = "StudentCalendar" - case attendance = "Attendance" - case gradebook = "Gradebook" - case studentHWNotes = "StudentHWNotes" - case studentInfo = "StudentInfo" - case studentClassList = "StudentClassList" - case studentSchoolInfo = "StudentSchoolInfo" - case getReportCardInitialData = "GetReportCardInitialData" - case getReportCardDocumentData = "GetReportCardDocumentData" - case getStudentDocumentInitialData = "GetStudentDocumentInitialData" - case getContentOfAttachedDoc = "GetContentOfAttachedDoc" - case synergyMailGetAttachment = "SynergyMailGetAttachment" - case updatePXPMessage = "UpdatePXPMessage" - case studentHealthInfo = "StudentHealthInfo" - - case getSupportedLanguages = "GetSupportedLanguages" - case getSoundFileData = "GetSoundFileData" - } - - /// Web services that StudentVue uses. `HDInfoServices` is only used to access the`HDInfoCommunication` endpoint - public enum WebServices: String { - case pxpWebServices = "PXPWebServices" - case hdInfoServices = "HDInfoServices" - } - + /// The domain of the StudentVue API. private var domain: String - /// The base URL to access StudentVue's APIs + + /// The base URL to access StudentVue's API. private var url: String { "https://\(domain)/Service/" } - /// The username to log into StudentVue's API + /// The username to log into StudentVue's API. private var username: String - /// The password to log into StudentVue's API + + /// The password to log into StudentVue's API. private var password: String - /// Creates a new URLSession for the library to use + /// Creates a new URLSession for the API section of the library to use. private let session: URLSession - /// Initializes a new StudentVueApi client with user credientials + /// Initializes a new ``StudentVueApi`` client with user credientials. + /// + /// Although this initializers may be used directly, it is best to use + /// ``StudentVue/StudentVue/init(domain:username:password:)``. There are several methods + /// contained by ``StudentVueApi`` that are only accessable through ``StudentVue/StudentVue``. /// /// - Parameters: - /// - domain: Domain of the school that uses StudentVue. Should be something like `something.edupoint.com` - /// - username: The username of the student's information to access - /// - password: The password of the student's information to access + /// - domain: Domain of the school that uses StudentVue. + /// - username: The username of the student's information to access. + /// - password: The password of the student's information to access. public init(domain: String, username: String, password: String) { self.domain = domain @@ -86,19 +70,25 @@ public class StudentVueApi { self.session = URLSession(configuration: sessionConfig) } - /// Retrieves account details as a hash + /// Retrieves account details as a hash. + /// + /// This is an internal function that should only be used by and accessed from + /// ``StudentVue/StudentVue/getAccountHash()``. /// /// - Returns: Hash of username, password, and domain internal func getAccountHash() -> String { return AccountHasher.hash(username: username, password: password, domain: domain) } - /// Updates the credentials of the user + /// Updates the credentials of the user. + /// + /// This is an internal function that should only be used by and accessed from + /// ``StudentVue/StudentVue/updateCredentials(domain:username:password:)``. /// /// - Parameters: - /// - domain: The new domain - /// - username: The new username - /// - password: The new password + /// - domain: The new domain. + /// - username: The new username. + /// - password: The new password. internal func updateCredentials(domain: String? = nil, username: String? = nil, password: String? = nil) { if let domain = domain { self.domain = domain @@ -113,17 +103,44 @@ public class StudentVueApi { } } - /// Lowest level function to access StudentVue's API + /// Lowest level function to access StudentVue's API. + /// + /// This method may be directly called for certain use cases, but most of the time, it is + /// better to call dedicated methods created by ``StudentVueApi``. Most of the StudentVue API + /// methods are handled with proper structures and typings. However, there are certain methods + /// not covered, which makes this method useful. To use API methods not covered by + /// ``StudentVueApi/Methods``, you can create an extension of the enum. + /// + /// ```swift + /// extension StudentVueApi.Methods { + /// static let anotherMethod = StudentVueApi.Methods(rawValue: "AnotherMethod") + /// } + /// + /// if let anotherMethod = StudentVueApi.Methods.anotherMethod { + /// let item = try await client.api.makeServiceRequest(methodName: anotherMethod) + /// } + /// ``` + /// + /// Although it is optional, the `params` parameter has a specific structure that needs to be + /// follow. It uses a nested dictionary where the outer key is the tag name. The inner + /// dictionary are the attributes, where the key `Value` is the tag content. + /// + /// ```swift + /// let params = ["ReportPeriod": ["Value": "S1"]] + /// + /// try await client.makeServiceRequest(methodName: .gradebook, params: params) + /// ``` /// /// - Parameters: - /// - endpoint: The endpoint to access - /// - methodName: The method to use, determing what data is being requested or sent - /// - serviceHandle: The service handle to use - /// - params: Parameters to be sent. Uses a nested dictionary where the outer key is the tag name. The inner dictionary are the attributes, where the key "Value" is the tag content + /// - endpoint: The endpoint to access. + /// - methodName: The method to use, determing what data is being requested or sent. + /// - serviceHandle: The service handle to use. + /// - params: Parameters to be sent. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession or no + /// response returned. /// - /// - Returns: The string format of the XML returned from the StudentVue API + /// - Returns: A string in the format of an XML returned from the StudentVue API. public func makeServiceRequest(endpoint: Endpoints = .pxpCommunication, methodName: Methods, serviceHandle: WebServices = .pxpWebServices, @@ -159,17 +176,50 @@ public class StudentVueApi { } } - /// Higher level function to access StudentVue's API. Automatically throws custom errors + /// Higher level function to access StudentVue's API. + /// + /// This function is a wrapper around + /// ``StudentVueApi/makeServiceRequest(endpoint:methodName:serviceHandle:params:)``, but + /// simply parses the output with ``SWXMLHash/XMLHash/parse(soapString:)`` for easier output handling + /// and contains more customized error messages that can be caught. + /// + /// This method may be directly called for certain use cases, but most of the time, it is + /// better to call dedicated methods created by ``StudentVueApi``. Most of the StudentVue API + /// methods are handled with proper structures and typings. However, there are certain methods + /// not covered, which makes this method useful. To use API methods not covered by + /// ``StudentVueApi/Methods``, you can create an extension of the enum. + /// + /// ```swift + /// extension StudentVueApi.Methods { + /// static let anotherMethod = StudentVueApi.Methods(rawValue: "AnotherMethod") + /// } + /// + /// if let anotherMethod = StudentVueApi.Methods.anotherMethod { + /// let item = try await client.api.makeServiceRequest(methodName: anotherMethod) + /// } + /// ``` + /// + /// Although it is optional, the `params` parameter has a specific structure that needs to be + /// follow. It uses a nested dictionary where the outer key is the tag name. The inner + /// dictionary are the attributes, where the key `Value` is the tag content. + /// + /// ```swift + /// let params = ["ReportPeriod": ["Value": "S1"]] + /// + /// try await client.makeServiceRequest(methodName: .gradebook, params: params) + /// ``` /// /// - Parameters: - /// - endpoint: The endpoint to access - /// - methodName: The method to use, determing what data is being requested or sent - /// - serviceHandle: The service handle to use - /// - params: Parameters to be sent. Uses a nested dictionary where the outer key is the tag name. The inner dictionary are the attributes, where the key "Value" is the tag content + /// - endpoint: The endpoint to access. + /// - methodName: The method to use, determing what data is being requested or sent. + /// - serviceHandle: The service handle to use. + /// - params: Parameters to be sent. /// - /// - Throws: `Error` or `StudentVueErrors`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or `StudentVueErrors`. The most common error that will be thrown is + /// ``StudentVueErrors/invalidCredentials``. An error thrown by URLSession or no + /// response returned. /// - /// - Returns: The XMLIndexer parsed from the response from the StudentVue API + /// - Returns: The XMLIndexer parsed from the response from the StudentVue API. public func xmlServiceRequest(endpoint: Endpoints = .pxpCommunication, methodName: Methods, serviceHandle: WebServices = .pxpWebServices, @@ -182,11 +232,14 @@ public class StudentVueApi { return try XMLHash.parse(soapString: result) } - /// Checks validity of user credentials quickly + /// Checks validity of user credentials quickly. + /// + /// This is an internal function that should only be used by and accessed from + /// ``StudentVue/StudentVue/checkCredentials()``. /// - /// - Throws: `Error` some other error has occured when api request was sent + /// - Throws: `Error` some other error has occured when api request was sent. /// - /// - Returns: Success or not + /// - Returns: Valid credentials or not. internal func checkCredentials() async throws -> Bool { do { _ = try await xmlServiceRequest(methodName: .getSoundFileData) @@ -201,11 +254,24 @@ public class StudentVueApi { /// Gets districts near the given zip code /// - /// - Parameter zip: The zip code to search for near-by districts that use StudentVue + /// This method retrieves nearby districts based on zip codes. Zip codes with no nearby + /// districts will return ``Districts`` with an empty list. This function can be accessed + /// without credentials or logging in. + /// + /// ```swift + /// let districts = try await StudentVue.getDistricts(zip: "11001") + /// ``` + /// + /// - Warning: This API endpoint has a rate limit of about 50-60 requests per minute, and will + /// result a 1 minute timeout. This issue can be accidently caused when having the + /// district bound to a variable in `SwiftUI` and calling this function. + /// + /// - Parameter zip: The zip code to search for near-by districts that use StudentVue. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: Information about the near-by district + /// - Returns: Information about the near-by district. static public func getDistricts(zip: String) async throws -> Districts { let studentVueClient = StudentVueApi(domain: "support.edupoint.com", username: "EdupointDistrictInfo", password: "Edup01nt") let districts = try await studentVueClient.makeServiceRequest(endpoint: .hdInfoCommunication, @@ -220,40 +286,47 @@ public class StudentVueApi { return try Districts(string: districts) } - /// Get all messages recently sent to the student + /// Get all messages recently sent to the student. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: All message information recently sent to the student + /// - Returns: All message information recently sent to the student. public func getMessages() async throws -> PXPMessages { try PXPMessages(string: await makeServiceRequest(methodName: .getPXPMessages)) } - /// Gets all recent calendar events such as assignments and breaks + /// Gets all recent calendar events such as assignment due dates and school breaks. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: All recent calendar events + /// - Returns: All recent calendar events. public func getCalendar() async throws -> StudentCalendar { try StudentCalendar(string: await makeServiceRequest(methodName: .studentCalendar)) } - /// Gets every absence along with other absence information + /// Gets every absence along with other absence information. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Important: This method does not return if the student was in the class, only if they were + /// abscent, tardy, or had an activity. /// - /// - Returns: Every absence by date along with tardies + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. + /// + /// - Returns: Every absence by date along with tardies and activity abscenses. public func getAttendence() async throws -> Attendance { try Attendance(string: await makeServiceRequest(methodName: .attendance)) } - /// Get all items in the gradebook + /// Get all classes and assignments in the gradebook. /// - /// - Parameter reportPeriod: The grading period to get. Default is the current grading period + /// - Parameter reportPeriod: The grading period to get. Default is the current grading period. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: All grades and grading period dates + /// - Returns: All grades and grading period dates. public func getGradeBook(reportPeriod: String? = nil) async throws -> GradeBook { var params: [String: [String: String]] = [:] @@ -264,31 +337,43 @@ public class StudentVueApi { return try GradeBook(string: await makeServiceRequest(methodName: .gradebook, params: params)) } - /// Currently unknown what this does + /// Currently unknown what this does. + /// + /// - Warning: No information is returned. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Experiment: Try using ``makeServiceRequest(endpoint:methodName:serviceHandle:params:)`` + /// if student's district uses this feature to see the response. /// - /// - Returns: Unknown, partially empty data struct + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. + /// + /// - Returns: Unknown, partially empty data struct. public func getClassNotes() async throws -> StudentHWNotes { try StudentHWNotes(string: await makeServiceRequest(methodName: .studentHWNotes)) } - /// Gets all of student's information stored + /// Gets all of student's information stored. + /// + /// Most of the information stored is information the student's parent when signing up the + /// student for the school year. This includes doctor and dentist information, and + /// emergency contacts among other values. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: All student information stored in StudentVue, such as name, birthdate, and address + /// - Returns: All student information stored in StudentVue, such as name, birthdate, and address. public func getStudentInfo() async throws -> StudentInfo { try StudentInfo(string: await makeServiceRequest(methodName: .studentInfo)) } - /// Gets all classes that are being taken along with current day's schedule + /// Gets all classes that are being taken along with current day's schedule. /// - /// - Parameter termIndex: The term to get the schedule for + /// - Parameter termIndex: The term to get the schedule for. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: All necessary class schedule information such as start/end times, room numbers, teachers, etc + /// - Returns: All necessary class schedule information such as start/end times,, teachers, etc. public func getClassSchedule(termIndex: String? = nil) async throws -> ClassSchedule { var params: [String: [String: String]] = [:] @@ -299,31 +384,62 @@ public class StudentVueApi { return try ClassSchedule(string: await makeServiceRequest(methodName: .studentClassList, params: params)) } - /// Gets information about the school and district + /// Gets information about the school and the school's district. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: School and district representitives with their contact information + /// - Returns: School and district staff and representitives with their contact + /// information and position. public func getSchoolInfo() async throws -> SchoolInfo { try SchoolInfo(string: await makeServiceRequest(methodName: .studentSchoolInfo)) } - /// Get a list of report cards. Can be used to get a report card using `getReportCard` + /// Get a list of report cards. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// This method can be used with ``getReportCard(documentGU:)`` to retrieve a specific report + /// card. /// - /// - Returns: A list of report card information and document GUs + /// - Note: The output of this method has not been validated, proceed with caution. + /// + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. + /// + /// - Returns: A list of report card information and document GUs. public func listReportCards() async throws -> ReportCards { try ReportCards(string: await makeServiceRequest(methodName: .getReportCardInitialData)) } - /// Get a report card based on its document GU + /// Get a report card based on its report card GU. + /// + /// Report card GUs can be retrieved using ``listReportCards()``. /// - /// - Parameter documentGU: The document GU of the report card to access. Can be retrieved with `listReportCards` + /// ```swift + /// let reportCards = try await client.api.listReportCards() /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// let docGU = reportCards.rcReportingPeriods[0].documentGU /// - /// - Returns: The report card in base64code + /// let reportCard = try await client.api.getReportCard(documentGU: docGU) + /// ``` + /// + /// The results from this method can be converted into a PDF file by first converting to type + /// `Data` and then writing to file. + /// + /// ```swift + /// if let data = Data(base64Encoded: reportCard.base64Code), + /// let url = URL(string: reportCard.fileName) { + /// data.write(to: url) + /// } + /// ``` + /// + /// - Note: The output of this method has not been validated, proceed with caution. + /// + /// - Parameter documentGU: The document GU of the report card to access. + /// + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. + /// + /// - Returns: The report card in struct containing a Base64 string. public func getReportCard(documentGU: String) async throws -> ReportCard { try ReportCard(string: await makeServiceRequest(methodName: .getReportCardDocumentData, params: ["DocumentGU": ["Value": documentGU]] @@ -331,22 +447,49 @@ public class StudentVueApi { ) } - /// Gets a list of document information. Can be used to get a document with `getDocument` + /// Gets a list of documents and their metadata. + /// + /// This method can be used with ``getDocument(documentGU:)`` to retrieve a specific document. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Note: The output of this method has not been validated, proceed with caution. /// - /// - Returns: A list of document GUs and other relevant document information + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession or no response returned + /// + /// - Returns: A list of document GUs and other relevant document information. public func listDocuments() async throws -> StudentDocuments { try StudentDocuments(string: await makeServiceRequest(methodName: .getStudentDocumentInitialData)) } - /// Gets a document based on the given GU + /// Gets a document based on a given document GU. + /// + /// Docment GUs can be retrieved using ``listDocuments()``. + /// + /// ```swift + /// let documents = try await client.api.listDocuments() + /// + /// let docGU = documents.studentDocumentDatas[0].documentGU + /// + /// let document = try await client.api.getDocument(documentGU: docGU) + /// ``` + /// + /// The results from this method can be converted into a PDF file by first converting to type + /// `Data` and then writing to file. + /// + /// ```swift + /// if let data = Data(base64Encoded: document.base64Code), + /// let url = URL(string: document.fileName) { + /// data.write(to: url) + /// } + /// ``` /// - /// - Parameter documentGU: The document GU of the document to access. Can be retrieved with `listDocuments` + /// - Note: The output of this method has not been validated, proceed with caution. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Parameter documentGU: The document GU of the document to access. /// - /// - Returns: The document in base64code and other relevent document information + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. + /// + /// - Returns: The document in Base64 and other relevent document information. public func getDocument(documentGU: String) async throws -> StudentAttachedDocumentData { try StudentAttachedDocumentData(string: await makeServiceRequest(methodName: .getContentOfAttachedDoc, params: ["DocumentGU": ["Value": documentGU]] @@ -354,13 +497,17 @@ public class StudentVueApi { ) } - /// Gets a message attachment based on its GU + /// Gets a message attachment based on its GU. + /// + /// - Warning: This method has not been tested. Returning data may be malformed + /// or throw an error. /// - /// - Parameter smAttachmentGU: The GU of the attachment to get + /// - Parameter smAttachmentGU: The GU of the attachment to get. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: The document in base64code along with its name + /// - Returns: The document in Base64 string along with its name. public func getMessageAttachment(smAttachmentGU: String) async throws -> MessageAttachment { try MessageAttachment(string: await makeServiceRequest(methodName: .synergyMailGetAttachment, params: ["SmAttachmentGU": ["Value": smAttachmentGU]] @@ -368,16 +515,19 @@ public class StudentVueApi { ) } - /// Updates a message's status + /// Updates a message's status. + /// + /// - Warning: This method has not been tested and may not return a desired result. /// /// - Parameters: - /// - messageID: The ID of the message to update - /// - type: The type of message that is to be updated - /// - markAsRead: To mark the message as read or not. This should be kept `true` + /// - messageID: The ID of the message to update. + /// - type: The type of message that is to be updated. + /// - markAsRead: To mark the message as read or not. This should be kept `true`. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: The raw response of the request in dictionary form + /// - Returns: The raw response of the request in dictionary form. public func updateMessage(messageID: String, type: String, markAsRead: Bool = true) async throws -> XMLIndexer { try XMLHash.parse(soapString: await makeServiceRequest(methodName: .updatePXPMessage, params: ["MessageListing": ["ID": messageID, "Type": type, "MarkAsRead": String(markAsRead)] @@ -386,17 +536,34 @@ public class StudentVueApi { ) } - /// Gets the student's health records + /// Gets the student's health records. + /// + /// This method will return immunization records along with health conditions + /// and health visitations. Specific health information can be included by enabling + /// it through certain `Bool` parameters. By default, only `healthImmunizations` is enabled. + /// + /// ```swift + /// let healthInfo = try await client.api.getHealthInfo(healthConditions: false, + /// healthVisits: true, + /// healthImmunizations: true) + /// ``` + /// + /// - Warning: The data structure of `healthConditions` and `healthVisits` are currently + /// unknown and accessing those methods may throw an error or provide an uknown + /// result. /// /// - Parameters: - /// - healthConditions: Whether to access the health conditions of the student or not. Current data structure unknown - /// - healthVisits: Whether to access the health visits of the student or not. Current data struct unknown - /// - healthImmunizations: Whether to access the immunization records of the student or not + /// - healthConditions: Whether to access the health conditions of the student or not. + /// - healthVisits: Whether to access the health visits of the student or not. + /// - healthImmunizations: Whether to access the immunization records of the student or not. /// - /// - Throws: `Error` or `StudentVueErrors.emptyResponse`. An error thrown by URLSession or no response returned + /// - Throws: `Error` or ``StudentVueErrors/emptyResponse``. An error thrown by URLSession + /// or no response returned. /// - /// - Returns: The health information of the student - public func getHealthInfo(healthConditions: Bool = false, healthVisits: Bool = false, healthImmunizations: Bool = true) async throws -> StudentHealthInfo { + /// - Returns: The health information of the student. + public func getHealthInfo(healthConditions: Bool = false, + healthVisits: Bool = false, + healthImmunizations: Bool = true) async throws -> StudentHealthInfo { try StudentHealthInfo(string: await makeServiceRequest(methodName: .studentHealthInfo, params: ["HealthConditions": ["Value": String(healthConditions)], "HealthVisits": ["Value": String(healthVisits)], diff --git a/Sources/StudentVue/SOAPApi/Utils/AccessPoints.swift b/Sources/StudentVue/SOAPApi/Utils/AccessPoints.swift new file mode 100644 index 0000000..d1b7584 --- /dev/null +++ b/Sources/StudentVue/SOAPApi/Utils/AccessPoints.swift @@ -0,0 +1,87 @@ +// +// AccessPoints.swift +// StudentVue +// +// Created by TheMoonThatRises on 8/20/24. +// + +import Foundation + +extension StudentVueApi { + /// Endpoints that StudentVue uses for it's API. + /// + /// This enum is used to access different endpoints using + /// ``StudentVueApi/makeServiceRequest(endpoint:methodName:serviceHandle:params:)`` and + /// ``StudentVueApi/xmlServiceRequest(endpoint:methodName:serviceHandle:params:)``. + /// + /// New endpoints can be created by making an extension and creating a constant and accessed + /// by nil-unwrapping. + /// + /// ```swift + /// extension StudentVueApi.Endpoints { + /// static let newEndpoint = StudentVueApi.Endpoints(rawValue: "newEndpoint") + /// } + /// + /// if let newEndpoint = StudentVueApi.Endpoints.newEndpoint { + /// ... + /// } + /// ``` + public enum Endpoints: String, Equatable { + case pxpCommunication = "PXPCommunication" + + /// Only used for `support.edupoint.com` to access available districts. + case hdInfoCommunication = "HDInfoCommunication" + } + + /// SOAP methods that are accessable and useful to have. + /// + /// There are other methods available, but have either not be discovered + /// or are not thought to be useful enough to be added here. + /// + /// This enum is used to access different methods using + /// ``StudentVueApi/makeServiceRequest(endpoint:methodName:serviceHandle:params:)`` and + /// ``StudentVueApi/xmlServiceRequest(endpoint:methodName:serviceHandle:params:)``. + /// + /// New methods can be created by making an extension and creating a constant and accessed + /// by nil-unwrapping. + /// + /// ```swift + /// extension StudentVueApi.Methods { + /// static let newMethod = StudentVueApi.Methods(rawValue: "newMethod") + /// } + /// + /// if let newMethod = StudentVueApi.Endpoints.newMethod { + /// ... + /// } + /// ``` + public enum Methods: String { + case getMatchingDistrictList = "GetMatchingDistrictList" + case getPXPMessages = "GetPXPMessages" + case studentCalendar = "StudentCalendar" + case attendance = "Attendance" + case gradebook = "Gradebook" + case studentHWNotes = "StudentHWNotes" + case studentInfo = "StudentInfo" + case studentClassList = "StudentClassList" + case studentSchoolInfo = "StudentSchoolInfo" + case getReportCardInitialData = "GetReportCardInitialData" + case getReportCardDocumentData = "GetReportCardDocumentData" + case getStudentDocumentInitialData = "GetStudentDocumentInitialData" + case getContentOfAttachedDoc = "GetContentOfAttachedDoc" + case synergyMailGetAttachment = "SynergyMailGetAttachment" + case updatePXPMessage = "UpdatePXPMessage" + case studentHealthInfo = "StudentHealthInfo" + + case getSupportedLanguages = "GetSupportedLanguages" + case getSoundFileData = "GetSoundFileData" + } + + /// Web services that StudentVue uses. + public enum WebServices: String { + case pxpWebServices = "PXPWebServices" + + /// Only used to access the `HDInfoCommunication` endpoint. + case hdInfoServices = "HDInfoServices" + } + +} diff --git a/Sources/StudentVue/SOAPApi/Utils/Hash.swift b/Sources/StudentVue/SOAPApi/Utils/Hash.swift index a7193c0..23d6212 100644 --- a/Sources/StudentVue/SOAPApi/Utils/Hash.swift +++ b/Sources/StudentVue/SOAPApi/Utils/Hash.swift @@ -7,7 +7,30 @@ import CryptoKit +/// Account hashing methods. +/// +/// This is a convienance class that is used to convert the combination of username, password, +/// and domain into a hash string. +/// +/// This method is only used and accessable by ``StudentVue/StudentVue/getAccountHash()``. +/// +/// ```swift +/// let hash = AccountHasher.hash(username: "970011111", +/// password: "password", +/// domain: "test.edupoint.com") +/// ``` class AccountHasher { + /// Hashs the username, password, and domain. + /// + /// This method joins the username, password, and domain and passes the string into `SHA256` + /// to create the hash. `SHA256` is used for the best performance and security. + /// + /// - Parameters: + /// - username: Username to hash with. + /// - password: Password to hash with. + /// - domain: Domain to hash with. + /// + /// - Returns: Hashed combination of username, password, and domain. public static func hash(username: String, password: String, domain: String) -> String { if let data = (username + password + domain).data(using: .utf8) { let digest = SHA256.hash(data: data) diff --git a/Sources/StudentVue/SOAPApi/Utils/SoapXML.swift b/Sources/StudentVue/SOAPApi/Utils/SoapXML.swift index 2621247..cc33e7d 100644 --- a/Sources/StudentVue/SOAPApi/Utils/SoapXML.swift +++ b/Sources/StudentVue/SOAPApi/Utils/SoapXML.swift @@ -1,21 +1,54 @@ // // SoapXML.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/11/23. // import Foundation +/// Custom SOAP XML request creator. +/// +/// This constructs the data that is sent to StudentVue's SOAP API. Everything but `paramStr` +/// is required to have a value. +/// +/// ``StudentVueApi/makeServiceRequest(endpoint:methodName:serviceHandle:params:)`` automatically +/// generates and sends this data package. +/// +/// ```swift +/// let requestData = SoapXML(userID: "970011111", +/// password: "password", +/// skipLoginLog: true, +/// parent: false, +/// webServiceHandleName: .pxpWebServices, +/// methodName: .gradebook, +/// paramStr: [:]) +/// +/// requestData.formattedData +/// ``` struct SoapXML { + /// Username to authenticate with. var userID: String + + /// Password to authenticate with. var password: String + + /// To skip login log or not. var skipLoginLog: Bool + + /// Is the authenticating account a parent. var parent: Bool + + /// Web service to access. var webServiceHandleName: StudentVueApi.WebServices + + /// Data method to access. var methodName: StudentVueApi.Methods + + /// Parameters to send with the request. var paramStr: [String: [String: String]] + /// Formats the parameter into a HTML escaped string that is required by the SOAP API. private var formattedParamStr: String { var formattedStr = "<Parms>" @@ -35,6 +68,7 @@ struct SoapXML { return formattedStr + "</Parms>" } + /// The data formatted as an XML. var formatted: String { return """ @@ -54,6 +88,7 @@ struct SoapXML { """ } + /// The XML as a `Data` type to be able to send through network requests. var dataFormatted: Data { formatted.data(using: .utf8)! } diff --git a/Sources/StudentVue/SOAPApi/Utils/StudentVueErrors.swift b/Sources/StudentVue/SOAPApi/Utils/StudentVueErrors.swift index bfdb433..21153ae 100644 --- a/Sources/StudentVue/SOAPApi/Utils/StudentVueErrors.swift +++ b/Sources/StudentVue/SOAPApi/Utils/StudentVueErrors.swift @@ -1,6 +1,6 @@ // // ApiErrors.swift -// +// StudentVue // // Created by TheMoonThatRises on 4/11/23. // @@ -8,26 +8,36 @@ import Foundation extension StudentVueApi { + /// Custom error messages that the methods within ``StudentVueApi`` can throw. public enum StudentVueErrors: LocalizedError { + /// Requested endpoint url is unreachable. case unreachableURL(String) + + /// Response returned by StudentVue API was empty or unable to be converted to type `Data`. case emptyResponse - case clientNotIntialised + + /// Username passed is empty. case noUsername + + /// Password passed is empty. case noPassword + + /// Credentials passed in are invalid. case invalidCredentials + + /// A miscellaneous SOAP API error has occured. case soapError(String) } } extension StudentVueApi.StudentVueErrors { + /// Provides localization for the custom error messages. public var errorDescription: String? { switch self { case .unreachableURL(let string): return "Unable to reach domain: \(string)" case .emptyResponse: return "Empty response body" - case .clientNotIntialised: - return "StudentVue client has not been created" case .noUsername: return "No username provided" case .noPassword: diff --git a/Sources/StudentVue/Scraper/Models/Assets/API.swift b/Sources/StudentVue/Scraper/Models/Assets/API.swift index 7857ab8..f1a43e7 100644 --- a/Sources/StudentVue/Scraper/Models/Assets/API.swift +++ b/Sources/StudentVue/Scraper/Models/Assets/API.swift @@ -1,6 +1,6 @@ // // API.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/22/23. // @@ -8,11 +8,11 @@ import Foundation extension StudentVueScraper { - struct APIData: Decodable { + internal struct APIData: Decodable { var html: String } - struct APIResult: Decodable { + internal struct APIResult: Decodable { enum CodingKeys: String, CodingKey { case type = "__type" case error = "Error" @@ -26,7 +26,9 @@ extension StudentVueScraper { var dataType: String } - struct API: Decodable { + /// This parses scraped data from StudentVue's website and uses `CodingKeys` to convert + /// unlegable keys into more obvious names. + internal struct API: Decodable { enum CodingKeys: String, CodingKey { case result = "d" } diff --git a/Sources/StudentVue/Scraper/Models/Assets/LoadControlData.swift b/Sources/StudentVue/Scraper/Models/Assets/LoadControlData.swift index 492ea2a..b77c556 100644 --- a/Sources/StudentVue/Scraper/Models/Assets/LoadControlData.swift +++ b/Sources/StudentVue/Scraper/Models/Assets/LoadControlData.swift @@ -1,6 +1,6 @@ // // LoadControlData.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/19/23. // @@ -8,7 +8,7 @@ import Foundation extension StudentVueScraper { - struct LoadControlParams: Decodable { + internal struct LoadControlParams: Decodable { enum CodingKeys: String, CodingKey { case controlName = "ControlName" case hideHeader = "HideHeader" @@ -18,12 +18,13 @@ extension StudentVueScraper { var hideHeader: Bool } - struct LoadControlFocusArgs: Codable { + internal struct LoadControlFocusArgs: Codable { enum CodingKeys: String, CodingKey { case agu = "AGU" case orgYearGU = "OrgYearGU" - case viewName, studentGU, schoolID, classID, markPeriodGU, gradePeriodGU, subjectID, teacherID, assignmentID, - standardIdentifier, gradingPeriodGroup + case viewName, studentGU, schoolID, classID, + markPeriodGU, gradePeriodGU, subjectID, teacherID, + assignmentID, standardIdentifier, gradingPeriodGroup } var viewName: String? @@ -41,7 +42,7 @@ extension StudentVueScraper { var gradingPeriodGroup: String? } - struct LoadControlData: Decodable { + internal struct LoadControlData: Decodable { enum CodingKeys: String, CodingKey { case loadParams = "LoadParams" case focusArgs = "FocusArgs" @@ -51,12 +52,12 @@ extension StudentVueScraper { var focusArgs: LoadControlFocusArgs } - struct SendableLoadControlRequest: Encodable { + internal struct SendableLoadControlRequest: Encodable { var control: String var parameters: LoadControlFocusArgs } - struct SendableLoadControlData: Encodable { + internal struct SendableLoadControlData: Encodable { var request: SendableLoadControlRequest } } diff --git a/Sources/StudentVue/Scraper/Models/Assets/LoginData.swift b/Sources/StudentVue/Scraper/Models/Assets/LoginData.swift index 76bed81..f688015 100644 --- a/Sources/StudentVue/Scraper/Models/Assets/LoginData.swift +++ b/Sources/StudentVue/Scraper/Models/Assets/LoginData.swift @@ -1,6 +1,6 @@ // // LoginData.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/8/23. // @@ -8,15 +8,19 @@ import Foundation extension StudentVueScraper { - struct LoginData { + internal struct LoginData { + /// This gets the current state of the website, which is used for login authentication. var vueState: VueState + /// The username of the the account to login. private var username: String { didSet { username = username.percentEncoding(withAllowedCharacters: .alphanumerics) .replacing("%20", with: "%2520") } } + + /// The password of the account to login. private var password: String { didSet { password = password.percentEncoding(withAllowedCharacters: .alphanumerics) @@ -24,7 +28,9 @@ extension StudentVueScraper { } } - var data: Data { + /// Compiles the information required to send to the website and converts it into + /// type `Data`. + public var data: Data { let value = "__VIEWSTATE=\(vueState.viewState)&__VIEWSTATEGENERATOR=\(vueState.viewStateGenerator)&" + "__EVENTVALIDATION=\(vueState.eventValidation)&" + "ctl00%24MainContent%24username=\(username)&ctl00%24MainContent%24password=\(password)&" + @@ -41,7 +47,11 @@ extension StudentVueScraper { .replacing("%20", with: "%2520") } - init(viewState: String, viewStateGenerator: String, eventValidation: String, username: String, password: String) { + init(viewState: String, + viewStateGenerator: String, + eventValidation: String, + username: String, + password: String) { self.init(vueState: VueState(viewState: viewState, viewStateGenerator: viewStateGenerator, eventValidation: eventValidation), diff --git a/Sources/StudentVue/Scraper/Models/Assets/NavigationData.swift b/Sources/StudentVue/Scraper/Models/Assets/NavigationData.swift index f5d3e83..901fd32 100644 --- a/Sources/StudentVue/Scraper/Models/Assets/NavigationData.swift +++ b/Sources/StudentVue/Scraper/Models/Assets/NavigationData.swift @@ -1,6 +1,6 @@ // // NavigationData.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/16/23. // @@ -9,7 +9,7 @@ import Foundation import SwiftSoup extension StudentVueScraper { - struct NavigationDataItem: Decodable { + internal struct NavigationDataItem: Decodable { var moduleIcon: String var activeClass: String var url: String @@ -19,7 +19,7 @@ extension StudentVueScraper { var enabled: Bool } - struct NavigationDataStudent: Decodable { + internal struct NavigationDataStudent: Decodable { var agu: String var name: String var sisNumber: String @@ -29,13 +29,14 @@ extension StudentVueScraper { var current: Bool } - struct NavigationData: Decodable { + internal struct NavigationData: Decodable { var items: [NavigationDataItem] var students: [NavigationDataStudent] } } extension StudentVueScraper.NavigationData { + /// Parses scraped StudentVue website for navigation data. init?(html: String) throws { let doc = try SwiftSoup.parse(html) diff --git a/Sources/StudentVue/Scraper/Models/Assets/VueState.swift b/Sources/StudentVue/Scraper/Models/Assets/VueState.swift index 09432f6..bb7a857 100644 --- a/Sources/StudentVue/Scraper/Models/Assets/VueState.swift +++ b/Sources/StudentVue/Scraper/Models/Assets/VueState.swift @@ -1,6 +1,6 @@ // // VueState.swift -// +// StudentVue // // Created by TheMoonThatRises on 3/8/23. // @@ -8,17 +8,24 @@ import SwiftSoup extension StudentVueScraper { - struct VueState { + internal struct VueState { + /// Gets the view state of the StudentVue website and encodes the alphanumerical values. var viewState: String { didSet { viewState = viewState.percentEncoding(withAllowedCharacters: .alphanumerics) } } + + /// Gets the view state generator value of the StudentVue website and encodes the + /// alphanumerical values. var viewStateGenerator: String { didSet { viewStateGenerator = viewStateGenerator.percentEncoding(withAllowedCharacters: .alphanumerics) } } + + /// Gets the event validation state value of the StudentVue website and encodes the + /// alphanumerical values. var eventValidation: String { didSet { eventValidation = eventValidation.percentEncoding(withAllowedCharacters: .alphanumerics) @@ -31,6 +38,7 @@ extension StudentVueScraper { self.eventValidation = eventValidation.percentEncoding(withAllowedCharacters: .alphanumerics) } + /// Parses the scraped contents of the StudentVue website for its state values. init(html: String) throws { let doc = try SwiftSoup.parse(html) diff --git a/Sources/StudentVue/Scraper/Models/CourseHistory/CourseHistoryData.swift b/Sources/StudentVue/Scraper/Models/CourseHistory/CourseHistoryData.swift index 9218e0d..21f8dc0 100644 --- a/Sources/StudentVue/Scraper/Models/CourseHistory/CourseHistoryData.swift +++ b/Sources/StudentVue/Scraper/Models/CourseHistory/CourseHistoryData.swift @@ -19,14 +19,28 @@ extension StudentVueScraper { case chsType = "CHSType" } + /// The ID of the class. public var courseID: String + + /// The name of the class. public var courseTitle: String + + /// Credits attempted from the class. public var creditsAttempted: String + + /// Credits successfully completed from the class. public var creditsCompleted: String + + /// Unknown. public var verifiedCredit: String + + /// Grade in the gradebook. public var mark: String + + /// Name of the grade school. Should be either primary or secondary school. public var chsType: String + /// Unique identifier for the structure. public var id: String { courseID } @@ -41,12 +55,22 @@ extension StudentVueScraper { case courses = "Courses" } + /// Name of the school. public var schoolName: String + + /// Year of attendance for the school. public var year: String + + /// Name of the term when the student took the school. public var termName: String + + /// Unknown. public var termOrder: Int + + /// List of courses taken during the term at the school. public var courses: [CourseData] + /// Unique identifier for the structure. public var id: String { schoolName + year + termName } @@ -59,10 +83,16 @@ extension StudentVueScraper { case terms = "Terms" } + /// Most recent grade level taken by the student. public var grade: String + + /// Unknown. public var gradeLevelOrder: Int + + /// List of terms. Includes all terms from primary to secondary school. public var terms: [CourseHistoryTerm] + /// Unique identifier of the structure. public var id: String { grade + String(gradeLevelOrder) + String(terms.count) } diff --git a/Sources/StudentVue/Scraper/Models/CourseHistory/ScraperCourseHistory.swift b/Sources/StudentVue/Scraper/Models/CourseHistory/ScraperCourseHistory.swift index 76a56ad..b095b2c 100644 --- a/Sources/StudentVue/Scraper/Models/CourseHistory/ScraperCourseHistory.swift +++ b/Sources/StudentVue/Scraper/Models/CourseHistory/ScraperCourseHistory.swift @@ -10,8 +10,12 @@ import SwiftSoup extension StudentVueScraper { public struct CourseHistory { + /// List of courses completed by the student. public var courseHistory: [CourseHistoryData] + /// Parses scraped HTML from the StudentVue website. + /// + /// - Parameter html: Scraped HTML StudentVue website. public init(html: String) async throws { let doc = try SwiftSoup.parse(html) diff --git a/Sources/StudentVue/Scraper/Models/GradeBook/ClassData.swift b/Sources/StudentVue/Scraper/Models/GradeBook/ClassData.swift index 8ed3a8e..fa948cf 100644 --- a/Sources/StudentVue/Scraper/Models/GradeBook/ClassData.swift +++ b/Sources/StudentVue/Scraper/Models/GradeBook/ClassData.swift @@ -9,21 +9,25 @@ import Foundation import SwiftSoup extension StudentVueScraper { + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct PointStruct { public var score: Double public var outOf: Double } + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct ClassGrades { public var mark: String public var count: Int } + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct GradeHistory { public var date: Date public var score: Int } + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct Weight { public var name: String public var weight: Int @@ -31,6 +35,7 @@ extension StudentVueScraper { public var percentOfTotal: Double } + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct ClassData { public var guid: String public var teacher: String @@ -52,6 +57,7 @@ extension StudentVueScraper { } } +@available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") extension StudentVueScraper.ClassData { init() { self.init(guid: "", @@ -86,6 +92,7 @@ extension StudentVueScraper.ClassData { } } +@available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") extension StudentVueScraper.Weight { public var computedWeight: Double { Double(weight) * (percent / 100) diff --git a/Sources/StudentVue/Scraper/Models/GradeBook/GradeData.swift b/Sources/StudentVue/Scraper/Models/GradeBook/GradeData.swift index 1e8a8bd..ccaf2c5 100644 --- a/Sources/StudentVue/Scraper/Models/GradeBook/GradeData.swift +++ b/Sources/StudentVue/Scraper/Models/GradeBook/GradeData.swift @@ -8,6 +8,7 @@ import Foundation extension StudentVueScraper { + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct GradeData: Decodable, Identifiable { enum CodingKeys: String, CodingKey { case teacher = "Teacher" diff --git a/Sources/StudentVue/Scraper/Models/GradeBook/ScraperGradeBook.swift b/Sources/StudentVue/Scraper/Models/GradeBook/ScraperGradeBook.swift index 7be7a41..7791dd4 100644 --- a/Sources/StudentVue/Scraper/Models/GradeBook/ScraperGradeBook.swift +++ b/Sources/StudentVue/Scraper/Models/GradeBook/ScraperGradeBook.swift @@ -9,7 +9,9 @@ import Foundation import SwiftSoup extension StudentVueScraper { + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public struct GradeBook { + /// List of classes currently taken with grades and assignments. public var classes: [ClassData] = [] public init(html: String, client: StudentVueScraper) async throws { diff --git a/Sources/StudentVue/Scraper/Models/Info/ScraperStudentInfo.swift b/Sources/StudentVue/Scraper/Models/Info/ScraperStudentInfo.swift index ec160e0..6a18959 100644 --- a/Sources/StudentVue/Scraper/Models/Info/ScraperStudentInfo.swift +++ b/Sources/StudentVue/Scraper/Models/Info/ScraperStudentInfo.swift @@ -9,6 +9,7 @@ import Foundation import SwiftSoup extension StudentVueScraper { + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.StudentInfo instead.") public struct StudentInfo { public var studentInfo: StudentInfoData diff --git a/Sources/StudentVue/Scraper/Models/Info/StudentInfoData.swift b/Sources/StudentVue/Scraper/Models/Info/StudentInfoData.swift index acf5158..4aa31ed 100644 --- a/Sources/StudentVue/Scraper/Models/Info/StudentInfoData.swift +++ b/Sources/StudentVue/Scraper/Models/Info/StudentInfoData.swift @@ -8,6 +8,7 @@ import Foundation extension StudentVueScraper { + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.StudentInfo instead.") public struct StudentInfoData { public var id: String public var name: String diff --git a/Sources/StudentVue/Scraper/StudentVueScraper.swift b/Sources/StudentVue/Scraper/StudentVueScraper.swift index 57cdd38..d11b7b1 100644 --- a/Sources/StudentVue/Scraper/StudentVueScraper.swift +++ b/Sources/StudentVue/Scraper/StudentVueScraper.swift @@ -8,15 +8,22 @@ import Foundation public class StudentVueScraper { + /// The domain of the StudentVue website. private var domain: String + /// The user agent to use when accessing scraping the StudentVue website. private static let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15" + + /// The base URL to access StudentVue's website. private var base: String { "https://\(domain)" } + /// The username to log into StudentVue's website. private var username: String + + /// The password to log into StudentVue's website. private var password: String public struct URLSessionResponse { @@ -30,30 +37,6 @@ public class StudentVueScraper { public var urlSessionResponse: URLSessionResponse } - // All of StudentVue's endpoint - public enum Endpoints: String { - case assessment = "/PXP2_Assessment.aspx" - case attendence = "/PXP2_Attendance.aspx" - case calendar = "/PXP2_Calendar.aspx" - case classSchedule = "/PXP2_ClassSchedule.aspx" - case conference = "/PXP2_Conference.aspx" - case courseHistory = "/PXP2_CourseHistory.aspx" - case courseRequest = "/PXP2_CourseRequest.aspx" - case graduationRequirements = "/PXP2_UserModule.aspx" - case digitalLocker = "/PXP2_DigitalLocker.aspx" - case fee = "/PXP2_Fee.aspx" - case gradeBook = "/PXP2_Gradebook.aspx" - case health = "/PXP2_Health.aspx" - case login = "/PXP2_Login_Student.aspx" - case mail = "/PXP2_Messages.aspx" - case mtss = "/PXP2_MTSS.aspx" - case reportCard = "/PXP2_ReportCard.aspx" - case schoolInformation = "/PXP2_SchoolInformation.aspx" - case studentInfo = "/PXP2_Student.aspx" - - case loadControl = "/service/PXP2Communication.asmx/LoadControl" - } - public enum HTTPMethods: String { case get, post } @@ -62,18 +45,22 @@ public class StudentVueScraper { case scrape, api } + /// Creates a new URLSession for the scraper section of the library to use. private let scraperSession: URLSession - /// Initializes a new StudentVueScraper client with user credientials + /// Initializes a new ``StudentVueScraper`` client with user credientials. /// - /// - Parameters: - /// - domain: Domain of the school that uses StudentVue. Should be something like `something.edupoint.com` - /// - username: The username of the student's information to access - /// - password: The password of the student's information to access + /// Although this initializers may be used directly, it is best to use + /// ``StudentVue/StudentVue/init(domain:username:password:)``. There are several methods + /// contained by ``StudentVueScraper`` that are only accessable through ``StudentVue/StudentVue``. /// - /// - Returns: A new StudentVueScraper client - init(domain: String, username: String, password: String) { + /// - Parameters: + /// - domain: Domain of the school that uses StudentVue. + /// - username: The username of the student's information to access. + /// - password: The password of the student's information to access. + public init(domain: String, username: String, password: String) { URLSession.shared.configuration.timeoutIntervalForRequest = 120.0 + self.domain = domain self.username = username self.password = password @@ -93,12 +80,15 @@ public class StudentVueScraper { self.scraperSession = URLSession(configuration: sessionConfig) } - /// Updates the credentials of the user + /// Updates the credentials of the user. + /// + /// This is an internal function that should only be used by and accessed from + /// ``StudentVue/StudentVue/updateCredentials(domain:username:password:)``. /// /// - Parameters: - /// - domain: The new domain - /// - username: The new username - /// - password: The new password + /// - domain: The new domain. + /// - username: The new username. + /// - password: The new password. internal func updateCredentials(domain: String? = nil, username: String? = nil, password: String? = nil) { if let domain = domain { self.domain = domain @@ -113,11 +103,22 @@ public class StudentVueScraper { } } - /// Build the header of the request with the corresponding type + /// Builds the header of the request with the corresponding type. + /// + /// Headers are different when scraping the API or the HTML content of the website. + /// + /// ```swift + /// var request = URLRequest(url: query) + /// + /// request.httpMethod = StudentVueScraper.HTTPMethods.get.rawValue + /// request.httpBody = data + /// + /// buildHeaders(request: &request, headerType: .scrape) + /// ``` /// /// - Parameters: - /// - request: The request to set headers - /// - headerType: The method the request is using to specialise the header + /// - request: The request to update the headers for. + /// - headerType: The method the request is using to specialise the header. private func buildHeaders(request: inout URLRequest, headerType: HeaderType) { var accept: String var contentType: String @@ -148,16 +149,24 @@ public class StudentVueScraper { request.setValue("\(base)\(Endpoints.login.rawValue)", forHTTPHeaderField: "Referer") } - /// Lowest level scraper call without throwing types + /// Lowest level scraper call. + /// + /// This method does not throw custom error types, and only throws URL requests errors. It is + /// recommended to use + /// ``StudentVueScraper/autoThrowApi(endpoint:method:headerType:data:urlParams:)`` + /// instead. + /// + /// - Important: It is required to first login with ``StudentVueScraper/login()`` before + /// accessing endpoints with ``StudentVueScraper``. /// /// - Parameters: - /// - endpoint: The endpoint to use - /// - method: The method to use when accessing the endpoint - /// - headerType: The way to access the endpoint - /// - data: Data to send to the endpoint - /// - urlParams: Encoded url params to pass + /// - endpoint: The endpoint to use. + /// - method: The method to use when accessing the endpoint. + /// - headerType: The way to access the endpoint. + /// - data: Data to send to the endpoint. + /// - urlParams: Encoded url params to pass. /// - /// - Returns: Data returned by the endpoint + /// - Returns: Data returned by the endpoint. public func api(endpoint: Endpoints, method: HTTPMethods = .get, headerType: HeaderType = .scrape, @@ -187,19 +196,28 @@ public class StudentVueScraper { return URLSessionResponse(data: response.0, response: response.1 as? HTTPURLResponse) } - /// Scraper call with specific error messages + /// Scraper call with specific error messages. + /// + /// This is a wrapper around + /// ``StudentVueScraper/api(endpoint:method:headerType:data:urlParams:)`` + /// with custom error messages and returns ``HTMLURLSessionResponse`` instead of + /// ``URLSessionResponse``. + /// + /// - Important: It is required to first login with ``StudentVueScraper/login()`` before + /// accessing endpoints with ``StudentVueScraper``. /// /// - Parameters: - /// - endpoint: The endpoint to use - /// - method: The method to use when accessing the endpoint - /// - headerType: The way to access the endpoint - /// - data: Data to send to the endpoint - /// - urlParams: Encoded url params to pass + /// - endpoint: The endpoint to use. + /// - method: The method to use when accessing the endpoint. + /// - headerType: The way to access the endpoint. + /// - data: Data to send to the endpoint. + /// - urlParams: Encoded url params to pass. /// - /// - Throws: `ScraperErrors.responseNot200` if the response code is not 200, `ScraperErrors.emptyData` if the returning data is empty, - /// or other misc parsing errors + /// - Throws: ``ScraperErrors/responseNot200`` if the response code is not 200, + /// ``ScraperErrors/emptyData`` if the returning data is empty, or other + /// misc parsing errors. /// - /// - Returns: Data returned by the endpoint + /// - Returns: Data returned by the endpoint. public func autoThrowApi(endpoint: Endpoints, method: HTTPMethods = .get, headerType: HeaderType = .scrape, @@ -224,22 +242,38 @@ public class StudentVueScraper { return HTMLURLSessionResponse(html: html, response: httpResponse, urlSessionResponse: response) } - /// Generates a new session id to scrape the website + /// Generates a new session id to scrape the website. + /// + /// - Throws: ``ScraperErrors/responseNot200`` if the response code is not 200, + /// ``ScraperErrors/emptyData`` if the returning data is empty, or other + /// misc parsing errors. /// - /// - Returns: The variable state of the website + /// - Returns: The variable state of the website. private func generateSessionId() async throws -> VueState { let getVueState = try await autoThrowApi(endpoint: .login, - method: .get, - urlParams: ["regenerateSessionId": "True"]) + method: .get, + urlParams: ["regenerateSessionId": "True"]) return try VueState(html: getVueState.html) } - /// Logs into StudentVue and sets the cookies + /// Logs into StudentVue and sets the cookies. + /// + /// This method will return the gradebook when the login finishes logging in. /// - /// - Throws: `ScraperErrors.noUsername` if no username was provided, or `ScraperErrors.noPassword` if no password was provided + /// - Important: It is required to first login before accessing endpoints with + /// ``StudentVueScraper``. /// - /// - Returns: The gradebook HTML if successful + /// - Warning: This method will take a long time to complete (~5s); it is recommended to + /// put this method call into a `Task`. + /// + /// - Throws: ``ScraperErrors/noUsername`` if no username was provided, or + /// ``ScraperErrors/noPassword`` if no password was provided. This may also throw + /// ``ScraperErrors/responseNot200`` if the response code is not 200, or + /// ``ScraperErrors/emptyData`` if the returning data is empty, or other + /// misc parsing errors. + /// + /// - Returns: The gradebook HTML if successful. public func login() async throws -> HTMLURLSessionResponse { guard !username.isEmpty else { throw ScraperErrors.noUsername @@ -255,26 +289,36 @@ public class StudentVueScraper { return try await autoThrowApi(endpoint: .login, method: .post, data: loginData.data) } - /// Log out of StudentVue + /// Logs out of StudentVue. + /// + /// - Important: Once logged out, accessing other endpoints may return an error without + /// logging in again with ``StudentVueScraper/login()``. /// - /// - Returns: True if logout was successful + /// - Returns: True if logout was successful. public func logout() async throws -> Bool { let response = try await api(endpoint: .login, method: .post, urlParams: ["Logout": "1"]) return response.response?.statusCode == 200 } - /// Retrieves gradebook by scraping the HTML + /// Retrieves gradebook by scraping the HTML. /// - /// - Returns: A class containing an array of `ClassData` + /// - Important: It is required to first login with ``StudentVueScraper/login()`` before + /// accessing this endpoint. + /// + /// - Returns: A class containing an array of ``ClassData``. + @available(swift, deprecated: 0.1.0, message: "Use StudentVueApi.GradeBook instead.") public func getGradeBook() async throws -> GradeBook { let response = try await autoThrowApi(endpoint: .gradeBook) return try await GradeBook(html: response.html, client: self) } - /// Retrieves course history by scraping the HTML + /// Retrieves course history by scraping the HTML. + /// + /// - Important: It is required to first login with ``StudentVueScraper/login()`` before + /// accessing this endpoint. /// - /// - Returns: A class containing an array of `CourseData` + /// - Returns: A class containing an array of ``CourseData``. public func getCourseHistory() async throws -> CourseHistory { let response = try await autoThrowApi(endpoint: .courseHistory) diff --git a/Sources/StudentVue/Scraper/Utils/AccessEndpoints.swift b/Sources/StudentVue/Scraper/Utils/AccessEndpoints.swift new file mode 100644 index 0000000..76c6b57 --- /dev/null +++ b/Sources/StudentVue/Scraper/Utils/AccessEndpoints.swift @@ -0,0 +1,34 @@ +// +// AccessEndpoints.swift +// StudentVue +// +// Created by TheMoonThatRises on 8/20/24. +// + +import Foundation + +extension StudentVueScraper { + /// All of StudentVue's website endpoints. + public enum Endpoints: String { + case assessment = "/PXP2_Assessment.aspx" + case attendence = "/PXP2_Attendance.aspx" + case calendar = "/PXP2_Calendar.aspx" + case classSchedule = "/PXP2_ClassSchedule.aspx" + case conference = "/PXP2_Conference.aspx" + case courseHistory = "/PXP2_CourseHistory.aspx" + case courseRequest = "/PXP2_CourseRequest.aspx" + case graduationRequirements = "/PXP2_UserModule.aspx" + case digitalLocker = "/PXP2_DigitalLocker.aspx" + case fee = "/PXP2_Fee.aspx" + case gradeBook = "/PXP2_Gradebook.aspx" + case health = "/PXP2_Health.aspx" + case login = "/PXP2_Login_Student.aspx" + case mail = "/PXP2_Messages.aspx" + case mtss = "/PXP2_MTSS.aspx" + case reportCard = "/PXP2_ReportCard.aspx" + case schoolInformation = "/PXP2_SchoolInformation.aspx" + case studentInfo = "/PXP2_Student.aspx" + + case loadControl = "/service/PXP2Communication.asmx/LoadControl" + } +} diff --git a/Sources/StudentVue/Scraper/Utils/ErrorPage.swift b/Sources/StudentVue/Scraper/Utils/ErrorPage.swift index abf4aad..8f282ec 100644 --- a/Sources/StudentVue/Scraper/Utils/ErrorPage.swift +++ b/Sources/StudentVue/Scraper/Utils/ErrorPage.swift @@ -9,7 +9,16 @@ import Foundation import SwiftSoup extension StudentVueScraper { - public struct ErrorPage { + internal struct ErrorPage { + /// Parses scraped HTML page from StudentVue checks for errors. + /// + /// This method checks for elements containing the id `USER_ERROR` or + /// `ctl00_MainContent_ERROR`, which are in the HTML when errors occur. + /// + /// - Parameter html: Scraped HTML page from StudentVue. + /// + /// - Throws: Either ``StudentVueScraper/ScraperErrors/unknown(message:)`` or + /// ``StudentVueScraper/ScraperErrors/invalidUsername`` depending on the error. public static func parse(html: String) throws { let doc = try SwiftSoup.parse(html) diff --git a/Sources/StudentVue/Scraper/Utils/ParseDate.swift b/Sources/StudentVue/Scraper/Utils/ParseDate.swift index 8127297..8f2b11e 100644 --- a/Sources/StudentVue/Scraper/Utils/ParseDate.swift +++ b/Sources/StudentVue/Scraper/Utils/ParseDate.swift @@ -8,7 +8,12 @@ import Foundation extension StudentVueScraper { - class ParseDate { + internal class ParseDate { + /// Reformats date to `M/d/yyyy`. + /// + /// - Parameter date: Input date to reformat. + /// + /// - Returns: The reformatted date or `nil` if unsuccessful. static public func getDate(date: String) -> Date? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "M/d/yyyy" diff --git a/Sources/StudentVue/Scraper/Utils/ScraperErrors.swift b/Sources/StudentVue/Scraper/Utils/ScraperErrors.swift index ca8d46f..eeef435 100644 --- a/Sources/StudentVue/Scraper/Utils/ScraperErrors.swift +++ b/Sources/StudentVue/Scraper/Utils/ScraperErrors.swift @@ -7,20 +7,39 @@ import Foundation extension StudentVueScraper { + /// Custom errors that methods within ``StudentVueScraper`` can throw. public enum ScraperErrors: Error { + /// Returning data is empty. case emptyData + + /// The password inputted is invalid. case incorrectPassword + + /// The username inputted is invalid. case invalidUsername + + /// No password provided. case noPassword + + /// No username provided. case noUsername + + /// No credentials provided. case noCredentials + + /// Website did not return HTML code 200. case responseNot200 + + /// HTML returned was unable to be parsed. case invalidWebsiteHTML + + /// An unknown error has occured. case unknown(message: String?) } } extension StudentVueScraper.ScraperErrors: LocalizedError { + /// Provides localizations for custom error messages. public var errorDescription: String? { switch self { case .emptyData: diff --git a/Sources/StudentVue/StudentVue.docc/GettingStarted.md b/Sources/StudentVue/StudentVue.docc/GettingStarted.md new file mode 100644 index 0000000..098b177 --- /dev/null +++ b/Sources/StudentVue/StudentVue.docc/GettingStarted.md @@ -0,0 +1,3 @@ +# Getting Started with StudentVue + +StudentVue.swift provides a simple yet powerful way to interact with StudentVue's SOAP api and contains capabailities for web scraping for more advance information. diff --git a/Sources/StudentVue/StudentVue.docc/StudentVue.md b/Sources/StudentVue/StudentVue.docc/StudentVue.md new file mode 100644 index 0000000..c8b202e --- /dev/null +++ b/Sources/StudentVue/StudentVue.docc/StudentVue.md @@ -0,0 +1,36 @@ +# ``StudentVue`` + +An easy and lightweight way to interact with StudentVue's' api. + +@Metadata { + @PageColor(green) +} + +## Overview + +StudentVue provides methods for interacting with StudentVue's api and website. The package also provides structures to easily access attributes and variables returned by StudentVue's SOAP api. You can easily enter create a StudentVue access instance using ``StudentVue/StudentVue``. The validity of the username and password can be checked using ``StudentVue/StudentVue/checkCredentials()``. User credentials and domains can also be easily updated with ``StudentVue/StudentVue/updateCredentials(domain:username:password:)``. + +## Featured + +@Links(visualStyle: detailedGrid) { + - + +} + +## Topics + +### Essentials + +- + +- ``StudentVue`` + +### API Access + +- ``StudentVueApi`` +- ``StudentVueApi/getGradeBook(reportPeriod:)`` + +### Scraper Access + +- ``StudentVueScraper`` +- ``StudentVueScraper/getCourseHistory()`` diff --git a/Sources/StudentVue/StudentVue.swift b/Sources/StudentVue/StudentVue.swift index d036364..aff1df1 100644 --- a/Sources/StudentVue/StudentVue.swift +++ b/Sources/StudentVue/StudentVue.swift @@ -7,39 +7,75 @@ import Foundation +/// Class representing StudentVue api access instance. +/// +/// Contains access points for both the ``StudentVueApi`` and ``StudentVueScraper``, which can be +/// used to interact with the StudentVue api or website. +/// +/// Information required to login and access information can set using the +/// ``init(domain:username:password:)`` initializer. Inputted credentials can then be checked with +/// ``checkCredentials()``. +/// +/// ```swift +/// let client = StudentVue(domain: "test.edupoint.com", +/// username: "970011111", +/// password: "password") +/// +/// do { +/// let isValidCredentials = try await client.checkCredentials() +/// } catch { +/// fatalError(String(describing: error)) +/// } +/// ``` public class StudentVue { - // Uses the official StudentVue api endpoint + /// Official StudentVue api endpoint. public private(set) var api: StudentVueApi - // Scrapes the StudentVue website + /// StudentVue website scraper. public private(set) var scraper: StudentVueScraper - // Client domain for other files to use + /// StudentVue connection domain. public private(set) static var domain = "" - /// Initializes a new StudentVue client with user credientials + /// Initializes a new StudentVue client with user credientials. + /// + /// This initializers also initialize and store ``StudentVueApi`` and ``StudentVueScraper``. /// /// - Parameters: - /// - domain: Domain of the school that uses StudentVue. Should be something like `something.edupoint.com` - /// - username: The username of the student's information to access - /// - password: The password of the student's information to access + /// - domain: Domain of StudentVue the school uses. + /// - username: The username of the account. + /// - password: The password of the account. public init(domain: String, username: String, password: String) { StudentVue.domain = domain self.api = StudentVueApi(domain: domain, username: username, password: password) self.scraper = StudentVueScraper(domain: domain, username: username, password: password) } - /// Retrieves account details as a hash + /// Retrieves account details as a hash. + /// + /// Since the username and password are inaccessible once set, this provides a way to check for + /// account similarities. /// - /// - Returns: Hash of username, password, and domain + /// The hash is created by joining the username, password, and domain and using `SHA256` to + /// generate a one-way hash. + /// + /// - Returns: Hash of joined username, password, and domain. public func getAccountHash() -> String { return api.getAccountHash() } - /// Updates the credentials of the user + /// Updates the credentials of the user. + /// + /// As a convience, the domain, username, and password can be updated individually. This method + /// will cascade down and update the credentials for ``StudentVueApi`` and + /// ``StudentVueScraper``. + /// + /// ```swift + /// client.updateCredentials(password: "my new password") + /// ``` /// /// - Parameters: - /// - domain: The new domain - /// - username: The new username - /// - password: The new password + /// - domain: The new domain. + /// - username: The new username. + /// - password: The new password. public func updateCredentials(domain: String? = nil, username: String? = nil, password: String? = nil) { if let domain = domain { StudentVue.domain = domain @@ -49,11 +85,18 @@ public class StudentVue { self.scraper.updateCredentials(domain: domain, username: username, password: password) } - /// Checks validity of user credentials quickly + /// Checks validity of user credentials. + /// + /// This method tests the validity of the combination of username, password, and domain by + /// attempting to access the student's uploaded sound file. The API will return a response with + /// an error code that is then caught and returned as a boolean here. + /// + /// Accessing the student's sound file is the fastest verification method, with only a 0.7 second + /// wait time. /// - /// - Throws: `Error` some other error has occured when api request was sent + /// - Throws: `Error` some other error has occured when api request was sent/ /// - /// - Returns: Success or not + /// - Returns: If the credentials are valid. public func checkCredentials() async throws -> Bool { return try await api.checkCredentials() }