hoursynccore 1.3.3

dotnet add package hoursynccore --version 1.3.3
                    
NuGet\Install-Package hoursynccore -Version 1.3.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="hoursynccore" Version="1.3.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="hoursynccore" Version="1.3.3" />
                    
Directory.Packages.props
<PackageReference Include="hoursynccore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add hoursynccore --version 1.3.3
                    
#r "nuget: hoursynccore, 1.3.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package hoursynccore@1.3.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=hoursynccore&version=1.3.3
                    
Install as a Cake Addin
#tool nuget:?package=hoursynccore&version=1.3.3
                    
Install as a Cake Tool

HourSyncCore

This project is licensed under the HourSync Licence v1.0.0. All code, including previous commits, is now licensed under the terms of the HourSync Licence v1.0.0. (HourSync Website) (GitHub link) Please be sure to read it and understand what you're agreeing to.


The elephant in the room: yes, 99% of the code overlaps with previous releases of HourSync. This was an easy way to trim some fat in the HourSync app and an easy way to allow others to use my work.

What is this?

HourSyncCore is a way to interface with the Academy Endorsement (colloquially called the eHours portal) website by the Olathe School District.

It interfaces easily with the website and makes it stupid simple for you to make your own version of HourSync.

The setup I've used below uses C# and WinUI 3 XAML. If you wish to use HourSyncCore in another style, you may have some extra setup, which I am not responsible for. You can fork this repository if you want to change it.

Features

  • Logging into the portal
  • Getting the eHours page (aka the home page)
  • Parsing the home page in an easy to manipulate format
  • Fetching specific requests
  • Editing pending requests
  • Fetching the leaderboard

Making requests will come in a future update.

Usage

Really easy.

This package is now on NuGet. Right click on your project in Visual Studio and click Manage NuGet Packages.... Search for HourSyncCore and install it.

Add the following using statement to the top of each file you use HourSync in:

using HourSyncCoreLib;

Logging in

Quite straightforward:

var loginResult = await HourSyncCore.Login(username, password);

if (!string.IsNullOrEmpty(loginResult.Error) || string.IsNullOrEmpty(loginResult.PhpSessionId))
{
    FileMgr.Log($"Login failed: {loginResult.Error}");
    ShowLoginError(loginResult.Error ?? "Login failed.");
    return;
}

HourSyncCore.Login returns 5 values:

  • PhpSessionId is the server's session ID. Store this for later use.
  • LoginHtmlResponse is only useful if an error has occurred. It is the "home" page upon sign-in.
  • StudentName is... the name of the student.
  • StudentAcademy is... the name of the academy the student is in.
  • Error is null unless an error has occurred.

Getting the List of past eHour Requests

Again, quite straightforward, just pass your session ID.

var getresp = await HourSyncCore.GetRequestsPage(phpSessionId);

HourSyncCore.GetRequestsPage simply returns the eHours requests page. This is unparsed. Luckily, HourSyncCore can parse them for you.

Parsing Requests

HourSyncCore parses eHour requests for you into an easy to use class called EHourRequest.

You can read the following 5 values:

  • Value is the button.value that the Academy Endorsement Portal uses. This is useful for manipulating specific requests.
  • Description is the title of your request.
  • Hours is how many hours the request was worth.
  • Date is... the date it was submitted.
  • State is whether it's accepted, denied, pending, or returned. This is not a string, but rather an enum Status
  • Body will be used in a future update, and it will be the content that you typed out for a request.

Call it like so:

// Add these lists to the top of your file
private List<EHourRequest> ReturnedRequests = [];
private List<EHourRequest> PendingRequests = [];
private List<EHourRequest> AcceptedRequests = [];
private List<EHourRequest> DeniedRequests = [];

// Parse
var parsed = Core.ParseRequests(getresp);

// And store
ReturnedRequests = parsed.Returned ?? new();
PendingRequests = parsed.Pending ?? new();
AcceptedRequests = parsed.Accepted ?? new();
DeniedRequests = parsed.Denied ?? new();

// Hint:
// getresp is the HTML response from GetRequestsPage()

Now you can use the description property (which is technically the title of the event) to create a layout:


xmlns:core="using:HourSyncCoreLib"


<StackPanel x:Name="RequestsPanel" Margin="0,0,0,10">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Pending Requests -" Margin="0,0,5,0"/>

        
        <TextBlock Text="{x:Bind PendingRequests.Count, Mode=OneWay}"/>
    </StackPanel>

    
    <ListView ItemsSource="{x:Bind PendingRequests, Mode=OneWay}" SelectionMode="None" Margin="-15, 5, 0, 0">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="core:EHourRequest">
                

                
                <Button
                    Tag="{x:Bind Value}"
                    HorizontalAlignment="Stretch"
                    HorizontalContentAlignment="Left"
                    Height="80"
                    Margin="0,0,0,10"
                    Click="RequestButton_Click">
                    <Button.Content>
                        
                        <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap">
                            <Run Text="{x:Bind Description}"/>
                            <LineBreak/>
                            <Run Text="Hours:"/>
                            <Run Text="{x:Bind Hours}"/>
                            <LineBreak/>
                            <Run Text="Date:"/>
                            <Run Text="{x:Bind Date}"/>
                        </TextBlock>
                    </Button.Content>
                </Button>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    
    
</StackPanel>

Fetching specific requests

HourSyncCore has a FetchedRequest result for HourSyncCore.GetRequest, which provides the following:

  • Value is the unique identifier for a request
  • Date is pretty obvious
  • Body is also pretty obvious: the content you typed
  • RequestedHours is a STRING, and should be easy to figure out what it is
  • Images is a string list of the image URLs from a request
  • Comments are... comments... from the teacher. I'm still yet to find a use for it
  • Success is a bool that tells you whether the request was successful or not
  • Error will have a value if something went wrong.
  • LoggedIn is a bool that helps to figure out why Success may be false

The one thing which HourSyncCore can NOT return is the title of the event; this is simply because the official portal does not have it on the request page for some odd reason. After using Core.ParseRequests as you should've in the previous step, you can map the event title to the request ID, or the other way around.

In the previous step, if you used the XAML binding, you can use the following code:

// I'd hope you're already using these using statements:
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

// or whatever your request button click event is
private void RequestButton_Click(object sender, RoutedEventArgs e)
{
    Button clickedButton = (Button)sender;
    string value = (string)clickedButton.Tag;
    // value is now the request's unique identifier

    // Now you can get the request
    FetchedEHourRequest response = await HourSyncCore.GetRequest(phpSessionId, value, true);
}

When submitting requests, Windows may introduce a dynamic quote (or sometimes other dynamic punctuation), which is not compatible with the encoding the server uses. When fetching requests via the portal or HourSync, this is reflected in the Body, where you may see stuff like �??. You can pass in the third argument as true or false to have HourSyncCore fix some of these funky characters (as ChatGPT called it, mojibake).

All of that aside, here's how you'd use it:

// you can use "var" in new versions of c#
FetchedEHourRequest response = await HourSyncCore.GetRequest(phpSessionId, id, true);

Here's an implementation of the GetRequest method:

if (response.Success){
    //do something:
    //note, HourSyncCore does NOT have these functions
    //these are just ideas as to what you can do with the returned values
    DescriptionBlock.Text = response.Body;
    DateBlock.Text = RelativeDateAndHoursParser.Parse(response.Date, response.RequestedHours);
    if (response.Images.Length>0){
        // Create a base URL for relative paths, since they are returned relatively
        // means image path is "../{image}.png", not "httsp://{website}.com/{image}.png"
        const string baseUrl = "https://academyendorsement.olatheschools.com/";

        foreach(string image in imagePaths){
            CreateAndAddImages(image)
        }
    }
}

Editing pending requests

This method is really simple to use because HourSyncCore provides an UpdatedRequest record for you to use:

UpdatedRequest updated = new (){
    Value = MyRequest.Value, // the unique identifier of the request
    NewContent = EditBox.Text, // the new content
    State = MyRequest.State // I would highly recommend NOT lying about this so that HourSyncCore can ensure that the request is actually editable. You can technically pass Status.Pending though.
}

Then, just call:

PostResponse response = HourSyncCore.EditRequest(phpSessionId, updated)

and it's done. HourSyncCore will only return if the operation was successful, any error, if you're logged in, and the HTML response.

Leaderboard

The leaderboard is an interesting addition to the portal and, at least for my academy, has some ghost users. Despite this, HourSyncCore provides two records for you to use: LeaderboardStudent and LeaderboardData. LeaderboardData is a list of LeaderboardStudent records.

The LeaderboardStudent record provides the name of the student as a string, the number of eHours they have as a string, and their rank (place) as an integer.

All you need to pass is your phpSessionId, and you can use x:Bind to make it easy to display the data:

//Add this in your Page or Window c#:
private LeaderboardData _leaderboardData;

public LeaderboardData leaderboardData
{
    get => _leaderboardData;
    set
    {
        _leaderboardData = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(leaderboardData)));
    }
}

public event PropertyChangedEventHandler PropertyChanged;

//Then get the data
leaderboardData = await HourSyncCore.FetchLeaderboard(phpSessionId);

And here's the XAML:


xmlns:core="using:HourSyncCoreLib"

<StackPanel Orientation="Vertical" Spacing="10">
    
    <TextBlock Text="Leaderboard"
           Style="{ThemeResource TitleTextBlockStyle}"
           Margin="0,0,0,10"/>

    
    <StackPanel Orientation="Horizontal" Spacing="20" Margin="14,0,0,0">
        <TextBlock Text="#"
               Style="{ThemeResource BodyStrongTextBlockStyle}"/>
        <TextBlock Text="Name"
           Style="{ThemeResource BodyStrongTextBlockStyle}"
           Width="250"/>
        <TextBlock Text="# of Hours"
           Style="{ThemeResource BodyStrongTextBlockStyle}"/>
    </StackPanel>

    
    <ListView ItemsSource="{x:Bind leaderboardData.StudentData, Mode=OneWay}" SelectionMode="Single">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="core:LeaderboardStudent">
                <StackPanel Orientation="Horizontal" Spacing="20">
                    <TextBlock Text="{x:Bind Rank}"
                               Style="{ThemeResource BodyStrongTextBlockStyle}"/>
                    <TextBlock Text="{x:Bind Name}"
                           Style="{ThemeResource BodyTextBlockStyle}"
                           Width="250"/>
                    <TextBlock Text="{x:Bind Hours}"
                           Style="{ThemeResource BodyTextBlockStyle}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackPanel>

HourSyncCore has XML documentations so Visual Studio intellisense will pick up on them and help you out.

Eventually, there will be a complete app that you can copy and paste, that has the barebones functions of HourSyncCore. Until then, just ask ChatGPT or read the docs yourself.

License

HourSyncCore is licensed under the same license as HourSync, which is <u>now the HourSync Licence v1.0.0</u>.

Please read the terms of the License and ensure that you understand what you're agreeing to. The License is in place to protect me, you, and the Olathe School District.

You can find the License at:

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.3.3 87 4/5/2026
1.3.2 84 4/4/2026
1.3.1 193 11/6/2025
1.3.0 185 9/22/2025
1.2.0 309 9/17/2025
1.0.0 231 8/31/2025