Skip to content

Commit 1f7fac5

Browse files
committed
Merge branch 'release/v1.0.0'
2 parents 97f19fe + 0fc35a6 commit 1f7fac5

16 files changed

Lines changed: 1409 additions & 63 deletions

API.Test/MessagesTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using WebUntisAPI.Client.Models.Messages;
7+
using static API.Test.AuthentificationTests;
8+
9+
namespace API.Test;
10+
11+
internal class MessagesTests
12+
{
13+
[Test]
14+
public void GetUnreadMessages()
15+
{
16+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
17+
18+
Task<int> messages = Client.GetUnreadMessagesCountAsync();
19+
messages.Wait();
20+
if (messages.Result == 0)
21+
Assert.Pass();
22+
else
23+
Assert.Fail();
24+
}
25+
26+
[Test]
27+
public void GetMessagePermissions()
28+
{
29+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
30+
31+
Task<MessagePermissions> permissions = Client.GetMessagePermissionsAsync();
32+
permissions.Wait();
33+
if (permissions.Result != null)
34+
Assert.Pass();
35+
else
36+
Assert.Fail();
37+
}
38+
39+
[Test]
40+
public void GetMessageInbox()
41+
{
42+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
43+
44+
Task<MessagePreview[]> messages = Client.GetMessageInboxAsync();
45+
messages.Wait();
46+
if (messages.Result.Length > 0)
47+
Assert.Pass();
48+
else
49+
Assert.Fail();
50+
}
51+
52+
[Test]
53+
public void GetFullMessage()
54+
{
55+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
56+
57+
Task<MessagePreview[]> messages = Client.GetMessageInboxAsync();
58+
messages.Wait();
59+
Task<Message> msg = messages.Result.First(msg => msg.Subject == "Test").GetFullMessageAsync(Client);
60+
msg.Wait();
61+
_ = msg.Result;
62+
return;
63+
}
64+
65+
[Test]
66+
public void GetReceptionPeople()
67+
{
68+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
69+
70+
Task<Dictionary<string, MessagePerson[]>> people = Client.GetMessagePeopleAsync();
71+
people.Wait();
72+
if (people.Result.Count > 0)
73+
Assert.Pass();
74+
else
75+
Assert.Fail();
76+
}
77+
78+
[Test]
79+
public void GetDrafts()
80+
{
81+
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
82+
83+
Task<DraftPreview[]> drafts = Client.GetSavedDraftsAsync();
84+
drafts.Wait();
85+
if (drafts.Result != null)
86+
Assert.Pass();
87+
else
88+
Assert.Fail();
89+
}
90+
}

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# WebUntisAPI.Client
22

3-
This .NET library allows you to connect you with a WebUntis account and load all of the data you need from there.<br>
3+
This .NET library allows you to connect you with a WebUntis account and load all of the data you need from there.
44

55
## Download sources:
66
- [![GitHub](https://img.shields.io/badge/GitHub-Releases-black)](https://github.com/Suiram1701/WebUntisAPI.Client/releases)
@@ -30,8 +30,8 @@ Remarks:
3030
- Under no circumstances should 10 req. per sec., more than 1800req. per hr (but in no case more than 3600 req. per hr). If the specifications are exceeded, access to WebUntis could permanently blocked by the WebUntis API.
3131

3232
### 3. Send requests
33-
After your login you can send requests to get informations about your timetable and all about.<br>
34-
The methods an what they do shoud be self-explained.<br>
33+
After your login you can send requests to get informations about your timetable and all about.
34+
The methods an what they do shoud be self-explained.
3535

3636
## Issues
3737
When you had an error that you don't understand or you don't understand how you can use the library you can create an issue so that I can help you by your problem.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
5+
namespace WebUntisAPI.Client.Converter
6+
{
7+
internal class APIDateTimeJsonConverter : JsonConverter<DateTime>
8+
{
9+
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
10+
{
11+
JToken token = JToken.Load(reader);
12+
return token.Value<DateTime>();
13+
}
14+
15+
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
16+
{
17+
string dateTimeString = value.ToString("yyyy-MM-dd") + "T" + value.ToString("HH:mm:ss");
18+
writer.WriteValue(dateTimeString);
19+
}
20+
}
21+
}

WebUntisAPI.Client/Extensions/DateTimeExtensions.cs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,25 @@ public static void ToWebUntisTimeFormat(this DateTime dateTime, out string dateS
3939
/// The time format is HHMM (Hour Minute).
4040
/// </para>
4141
/// </remarks>
42-
/// <param name="dateTime">Instance</param>
42+
/// <param name="_">Instance</param>
4343
/// <param name="dateString">Date string</param>
4444
/// <param name="timeString">Time string</param>
4545
/// <returns>The new instance that contains the given time. When not to throw on exception and an exception happened is the return value <see langword="null"/></returns>
4646
/// <exception cref="FormatException">Thrown when one of the given strings isn't in the right format</exception>
47-
public static DateTime FromWebUntisTimeFormat(this DateTime dateTime, string dateString, string timeString)
47+
public static DateTime FromWebUntisTimeFormat(this DateTime _, string dateString, string timeString)
4848
{
49-
Regex dateRegex = new Regex(@"^\d{4}(?:0\d|1[0-2])(?:0[1-9]|[1-2]\d|3[0-1])$"); // Regex for the WebUntis date format
50-
Regex timeRegex = new Regex(@"^(?:\d|1\d|2[0-3])[0-5]\d$"); // Regex for the WebUntis time format
51-
52-
// Check if the date- and time strings are valid
53-
bool isDateValid = dateRegex.IsMatch(dateString);
54-
bool isTimeValid = timeRegex.IsMatch(timeString);
55-
56-
if (!isDateValid || !isTimeValid)
57-
throw new FormatException($"The string {(isDateValid ? timeString : dateString)} isn't in the valid format!");
49+
Match dateMatch = Regex.Match(dateString, @"^(\d{4})(0\d|1[0-2])(0[1-9]|[1-2]\d|3[0-1])$");
50+
Match timeMatch = Regex.Match(timeString, @"^(\d|1\d|2[0-3])([0-5]\d)$");
5851

52+
if (!dateMatch.Success || !timeMatch.Success)
53+
throw new FormatException("A valid format was expected");
5954

60-
// Parse the numbers in the string to value
61-
int year = int.Parse(dateString.Substring(0, 4));
62-
int month = int.Parse(dateString.Substring(4, 2));
63-
int day = int.Parse(dateString.Substring(6, 2));
55+
int year = int.Parse(dateMatch.Groups[1].Value);
56+
int month = int.Parse(dateMatch.Groups[2].Value);
57+
int day = int.Parse(dateMatch.Groups[3].Value);
6458

65-
bool is4Letters = timeString.Length == 4;
66-
int hour = int.Parse(is4Letters ? timeString.Substring(0, 2) : timeString[0].ToString());
67-
int minute = int.Parse(is4Letters ? timeString.Substring(2, 2) : timeString.Substring(1, 2));
59+
int hour = int.Parse(timeMatch.Groups[1].Value);
60+
int minute = int.Parse(timeMatch.Groups[2].Value);
6861

6962
// date and time string to DateTime instance
7063
return new DateTime(year, month, day, hour, minute, 0);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Net.Http;
9+
using System.Text.RegularExpressions;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
namespace WebUntisAPI.Client.Models.Messages
14+
{
15+
/// <summary>
16+
/// An attachment from a message
17+
/// </summary>
18+
[DebuggerDisplay("{Name, nq}")]
19+
public struct Attachment
20+
{
21+
/// <summary>
22+
/// The file name of the attachment
23+
/// </summary>
24+
[JsonProperty("name")]
25+
public string Name { get; set; }
26+
27+
/// <summary>
28+
/// The attachment id
29+
/// </summary>
30+
[JsonProperty("id")]
31+
internal readonly string _id;
32+
33+
/// <summary>
34+
/// Get the content of the attachment as stream
35+
/// </summary>
36+
/// <param name="client">The client with them you got this instane (It doesn't have to be the same client but the same account)</param>
37+
/// <param name="timeout">The time after that the download of the content will cancelled (In miliseconds)</param>
38+
/// <param name="ct">Cancellation token</param>
39+
/// <returns>The attachment as stream</returns>
40+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
41+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
42+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
43+
public async Task<MemoryStream> DownloadContentAsStreamAsync(WebUntisClient client, int timeout = 2000, CancellationToken ct = default)
44+
{
45+
string storageResponseString = await client.MakeAPIGetRequestAsync($"/WebUntis/api/rest/view/v1/messages/{_id}/attachmentstorageurl", ct);
46+
47+
JObject data = JObject.Parse(storageResponseString);
48+
JArray headerArray = data.Value<JArray>("additionalHeaders");
49+
50+
HttpRequestMessage request = new HttpRequestMessage()
51+
{
52+
Method = HttpMethod.Get,
53+
RequestUri = new Uri(data.Value<string>("downloadUrl"))
54+
};
55+
56+
// Auth headers
57+
foreach (JObject jsonHeader in headerArray.Cast<JObject>())
58+
request.Headers.Add(jsonHeader.Value<string>("key"), jsonHeader.Value<string>("value"));
59+
60+
// Date header
61+
string dateStr = DateTime.Now.ToString("yyyyMMdd");
62+
string timeStr = DateTime.Now.ToString("HHmmss");
63+
request.Headers.Add("x-amz-date", $"{dateStr}T{timeStr}Z");
64+
65+
using (HttpClient downloadClient = new HttpClient())
66+
{
67+
downloadClient.Timeout = TimeSpan.FromMilliseconds(timeout);
68+
HttpResponseMessage response = await downloadClient.SendAsync(request, ct);
69+
70+
// Check cancellation token
71+
if (ct.IsCancellationRequested)
72+
return default;
73+
74+
// Verify response
75+
if (response.StatusCode != HttpStatusCode.OK)
76+
throw new HttpRequestException($"There was an error while the http request (Code: {response.StatusCode}).");
77+
78+
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
79+
{
80+
string detail = Regex.Match(await response.Content.ReadAsStringAsync(), @"<Message>([a-zA-z0-9\s]+)</Message>").Groups[1].Value; // Get the error message
81+
throw new UnauthorizedAccessException($"Invalid authentication. Detail: {detail}");
82+
}
83+
84+
return (MemoryStream)await response.Content.ReadAsStreamAsync();
85+
}
86+
}
87+
}
88+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using WebUntisAPI.Client.Converter;
6+
7+
namespace WebUntisAPI.Client.Models.Messages
8+
{
9+
/// <summary>
10+
/// A draft
11+
/// </summary>
12+
[DebuggerDisplay("Subject: {Subject, nq}, Sender: {Sender, nq}, Send time: {SentTime, nq}")]
13+
public class Draft
14+
{
15+
/// <summary>
16+
/// The id of the message
17+
/// </summary>
18+
[JsonProperty("id")]
19+
public int Id { get; set; }
20+
21+
/// <summary>
22+
/// The subject of the message
23+
/// </summary>
24+
[JsonProperty("subject")]
25+
public string Subject { get; set; }
26+
27+
/// <summary>
28+
/// The sender of the message (only for messages in the inbox)
29+
/// </summary>
30+
[JsonProperty("sender")]
31+
public MessagePerson Sender { get; set; } = null;
32+
33+
/// <summary>
34+
/// Is the message a reply
35+
/// </summary>
36+
[JsonProperty("isReply")]
37+
public bool IsReply { get; set; }
38+
39+
/// <summary>
40+
/// Can you reply the message
41+
/// </summary>
42+
[JsonProperty("isReplyAllowed")]
43+
public bool IsReplyAllowed { get; set; }
44+
45+
/// <summary>
46+
/// The send time of the message
47+
/// </summary>
48+
[JsonProperty("sentDateTime")]
49+
[JsonConverter(typeof(APIDateTimeJsonConverter))]
50+
DateTime SentTime { get; set; }
51+
52+
/// <summary>
53+
/// Is allowed to delete the message
54+
/// </summary>
55+
[JsonProperty("allowMessageDeletion")]
56+
public bool AllowMessageDeletion { get; set; }
57+
58+
/// <summary>
59+
/// Idk
60+
/// </summary>
61+
[JsonProperty("recipientGroups")]
62+
public List<object> RecipientGroups { get; set; }
63+
64+
/// <summary>
65+
/// All the recipients for a message (only for self-sends messages)
66+
/// </summary>
67+
/// <remarks>
68+
/// When its <see langword="null"/> is the recipient the current user
69+
/// </remarks>
70+
[JsonProperty("recipientPersons")]
71+
public List<MessagePerson> Recipients { get; set; } = null;
72+
73+
/// <summary>
74+
/// The full content of the draft
75+
/// </summary>
76+
[JsonProperty("content")]
77+
public string Content { get; set; }
78+
79+
/// <summary>
80+
/// The file attachments of the draft
81+
/// </summary>
82+
[JsonProperty("storageAttachments")]
83+
public List<Attachment> Attachments { get; set; } = new List<Attachment>();
84+
85+
/// <summary>
86+
/// The option for the recipient
87+
/// </summary>
88+
[JsonProperty("recipientOption")]
89+
public string RecipientOption { get; set; }
90+
91+
/// <summary>
92+
/// Forbid the reply (you need the permission to do that)
93+
/// </summary>
94+
[JsonProperty("forbidReply")]
95+
public bool ForbidReply { get; set; }
96+
97+
/// <summary>
98+
/// Idk
99+
/// </summary>
100+
[JsonProperty("copyToStudents")]
101+
public bool CopyToStudents { get; set; }
102+
}
103+
}

0 commit comments

Comments
 (0)