Skip to content

Commit 1a5900a

Browse files
committed
Add message inbox and support for messages read and attachments
1 parent f3e2d4d commit 1a5900a

9 files changed

Lines changed: 484 additions & 74 deletions

File tree

API.Test/MessagesTests.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void GetUnreadMessages()
1515
{
1616
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
1717

18-
Task<int> messages = Client.GetUnreadMessagesCountAsync();
18+
Task<int> messages = Client.MessageClient.GetUnreadMessagesCountAsync();
1919
messages.Wait();
2020
if (messages.Result == 0)
2121
Assert.Pass();
@@ -28,11 +28,37 @@ public void GetMessagePermissions()
2828
{
2929
Client.LoginAsync(s_Server, s_LoginName, s_UserName, s_Password).Wait();
3030

31-
Task<MessagePermissions> permissions = Client.GetMessagePermissionsAsync();
31+
Task<MessagePermissions> permissions = Client.MessageClient.GetMessagePermissionsAsync();
3232
permissions.Wait();
3333
if (permissions.Result != null)
3434
Assert.Pass();
3535
else
3636
Assert.Fail();
3737
}
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.MessageClient.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.MessageClient.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+
}
3864
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Text.RegularExpressions;
8+
using System.Threading.Tasks;
9+
10+
namespace WebUntisAPI.Client.Converter
11+
{
12+
internal class APIDateTimeJsonConverter : JsonConverter<DateTime>
13+
{
14+
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
15+
{
16+
JToken token = JToken.Load(reader);
17+
return token.Value<DateTime>();
18+
}
19+
20+
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
21+
{
22+
string dateTimeString = value.ToString("yyyy-MM-dd") + "T" + value.ToString("HH:mm:ss");
23+
writer.WriteValue(dateTimeString);
24+
}
25+
}
26+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net.Http;
7+
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using WebUntisAPI.Client.Models;
11+
12+
namespace WebUntisAPI.Client
13+
{
14+
/// <summary>
15+
/// A client with them you could load and send untis messages
16+
/// </summary>
17+
/// <remarks>
18+
/// 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.
19+
/// </remarks>
20+
public class MessageClient
21+
{
22+
private readonly WebUntisClient _client;
23+
24+
/// <summary>
25+
/// Create a new message client to access the untis messages
26+
/// </summary>
27+
/// <param name="client">The client from which account they want to receive the messages</param>
28+
public MessageClient(WebUntisClient client)
29+
{
30+
_client = client;
31+
}
32+
33+
/// <summary>
34+
/// Get the count of unread messages
35+
/// </summary>
36+
/// <param name="ct">Cancellation token</param>
37+
/// <returns>The count of unread messages</returns>
38+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
39+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
40+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
41+
public async Task<int> GetUnreadMessagesCountAsync(CancellationToken ct = default)
42+
{
43+
string responseString = await _client.MakeAPIGetRequestAsync("/WebUntis/api/rest/view/v1/messages/status", ct);
44+
return JObject.Parse(responseString).Value<int>("unreadMessagesCount");
45+
}
46+
47+
/// <summary>
48+
/// Get the permissions you have to send messages
49+
/// </summary>
50+
/// <param name="ct">Cancllation token</param>
51+
/// <returns></returns>
52+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
53+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
54+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
55+
public async Task<MessagePermissions> GetMessagePermissionsAsync(CancellationToken ct = default)
56+
{
57+
string responseString = await _client.MakeAPIGetRequestAsync("/WebUntis/api/rest/view/v1/messages/permissions", ct);
58+
return JsonConvert.DeserializeObject<MessagePermissions>(responseString);
59+
}
60+
61+
/// <summary>
62+
/// Get the you message inbox
63+
/// </summary>
64+
/// <param name="ct">Cancellation token</param>
65+
/// <returns>The message previews</returns>
66+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
67+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
68+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
69+
public async Task<MessagePreview[]> GetMessageInboxAsync(CancellationToken ct = default)
70+
{
71+
string responseString = await _client.MakeAPIGetRequestAsync("/WebUntis/api/rest/view/v1/messages", ct);
72+
73+
JArray jsonMsg = JObject.Parse(responseString).Value<JArray>("incomingMessages");
74+
return new JsonSerializer().Deserialize<List<MessagePreview>>(jsonMsg.CreateReader()).ToArray();
75+
}
76+
}
77+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Net.Http;
10+
using System.Net.Http.Headers;
11+
using System.Text;
12+
using System.Text.RegularExpressions;
13+
using System.Threading;
14+
using System.Threading.Tasks;
15+
16+
namespace WebUntisAPI.Client.Models
17+
{
18+
/// <summary>
19+
/// An attachment from a message
20+
/// </summary>
21+
[DebuggerDisplay("{Name, nq}")]
22+
public struct Attachment
23+
{
24+
/// <summary>
25+
/// The file name of the attachment
26+
/// </summary>
27+
[JsonProperty("name")]
28+
public string Name { get; set; }
29+
30+
/// <summary>
31+
/// The attachment id
32+
/// </summary>
33+
[JsonProperty("id")]
34+
private readonly string _id;
35+
36+
/// <summary>
37+
/// Get the content of the attachment as stream
38+
/// </summary>
39+
/// <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>
40+
/// <param name="timeout">The time after that the download of the content will cancelled (In miliseconds)</param>
41+
/// <param name="ct">Cancellation token</param>
42+
/// <returns>The attachment as stream</returns>
43+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
44+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
45+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
46+
public async Task<MemoryStream> DownloadContentAsStreamAsync(WebUntisClient client, int timeout = 2000, CancellationToken ct = default)
47+
{
48+
string storageResponseString = await client.MakeAPIGetRequestAsync($"/WebUntis/api/rest/view/v1/messages/{_id}/attachmentstorageurl", ct);
49+
50+
JObject data = JObject.Parse(storageResponseString);
51+
JArray headerArray = data.Value<JArray>("additionalHeaders");
52+
53+
HttpRequestMessage request = new HttpRequestMessage()
54+
{
55+
Method = HttpMethod.Get,
56+
RequestUri = new Uri(data.Value<string>("downloadUrl"))
57+
};
58+
59+
// Auth headers
60+
foreach (JObject jsonHeader in headerArray.Cast<JObject>())
61+
request.Headers.Add(jsonHeader.Value<string>("key"), jsonHeader.Value<string>("value"));
62+
63+
// Date header
64+
string dateStr = DateTime.Now.ToString("yyyyMMdd");
65+
string timeStr = DateTime.Now.ToString("HHmmss");
66+
request.Headers.Add("x-amz-date", $"{dateStr}T{timeStr}Z");
67+
68+
using (HttpClient downloadClient = new HttpClient())
69+
{
70+
downloadClient.Timeout = TimeSpan.FromMilliseconds(timeout);
71+
HttpResponseMessage response = await downloadClient.SendAsync(request, ct);
72+
73+
// Check cancellation token
74+
if (ct.IsCancellationRequested)
75+
return default;
76+
77+
// Verify response
78+
if (response.StatusCode != HttpStatusCode.OK)
79+
throw new HttpRequestException($"There was an error while the http request (Code: {response.StatusCode}).");
80+
81+
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
82+
{
83+
84+
string detail = Regex.Match(await response.Content.ReadAsStringAsync(), @"<Message>([a-zA-z0-9\s]+)</Message>").Groups[1].Value; // Get the error message
85+
throw new UnauthorizedAccessException($"Invalid authentication. Detail: {detail}");
86+
}
87+
88+
return (MemoryStream)await response.Content.ReadAsStreamAsync();
89+
}
90+
}
91+
}
92+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using WebUntisAPI.Client.Converter;
9+
10+
namespace WebUntisAPI.Client.Models
11+
{
12+
/// <summary>
13+
/// The full message
14+
/// </summary>
15+
public class Message
16+
{
17+
/// <summary>
18+
/// The id of the message
19+
/// </summary>
20+
[JsonProperty("id")]
21+
public int Id { get; set; }
22+
23+
/// <summary>
24+
/// The subject of the message
25+
/// </summary>
26+
[JsonProperty("subject")]
27+
public string Subject { get; set; }
28+
29+
/// <summary>
30+
/// The full content of the message
31+
/// </summary>
32+
[JsonProperty("content")]
33+
public string Content { get; set; }
34+
35+
/// <summary>
36+
/// The sender of the message
37+
/// </summary>
38+
[JsonProperty("sender")]
39+
public MessageProfile Sender { get; set; }
40+
41+
/// <summary>
42+
/// All the recipients for a message
43+
/// </summary>
44+
/// <remarks>
45+
/// When its <see langword="null"/> is the recipient the current user
46+
/// </remarks>
47+
[JsonProperty("recipients")]
48+
public List<MessageProfile> Recipients { get; set; } = null;
49+
50+
/// <summary>
51+
/// The send time of the message
52+
/// </summary>
53+
[JsonProperty("sentDateTime")]
54+
[JsonConverter(typeof(APIDateTimeJsonConverter))]
55+
public DateTime SentTime { get; set; }
56+
57+
/// <summary>
58+
/// Is allowed to delete the message
59+
/// </summary>
60+
[JsonProperty("allowMessageDeletion")]
61+
public bool AllowMessageDeletion { get; set; }
62+
63+
/// <summary>
64+
/// Is the message revoked
65+
/// </summary>
66+
[JsonProperty("isRevoked")]
67+
public bool IsRevoked { get; set; }
68+
69+
/// <summary>
70+
/// The file attachments of the message
71+
/// </summary>
72+
[JsonProperty("storageAttachments")]
73+
public List<Attachment> Attachments { get; set; }
74+
75+
/// <summary>
76+
/// Is the message a reply
77+
/// </summary>
78+
[JsonProperty("isReply")]
79+
public bool IsReply { get; set; } = false;
80+
81+
/// <summary>
82+
/// Can you reply the message
83+
/// </summary>
84+
[JsonProperty("isReplyAllowed")]
85+
public bool IsReplyAllowed { get; set; } = true;
86+
87+
/// <summary>
88+
/// Is this a report message
89+
/// </summary>
90+
[JsonProperty("isReportMessage")]
91+
public bool IsReportMessage { get; set; } = false;
92+
93+
/// <summary>
94+
/// Is a reply for this forbidden
95+
/// </summary>
96+
[JsonProperty("isReplyForbidden")]
97+
public bool IsReplyForbidden { get; set; } = false;
98+
99+
/// <summary>
100+
/// The history of all replies
101+
/// </summary>
102+
[JsonProperty("replyHistory")]
103+
public List<Message> ReplyHistory { get; set; } = new List<Message>();
104+
}
105+
}

0 commit comments

Comments
 (0)