Skip to content

Commit 1202f95

Browse files
committed
Add send, revoke and delete method for messages
1 parent 279f8d6 commit 1202f95

6 files changed

Lines changed: 213 additions & 14 deletions

File tree

WebUntisAPI.Client/Models/Messages/Attachment.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public struct Attachment
3131
/// The attachment id
3232
/// </summary>
3333
[JsonProperty("id")]
34-
public string Id { get; set; }
34+
internal readonly string _id;
3535

3636
/// <summary>
3737
/// Get the content of the attachment as stream
@@ -45,7 +45,7 @@ public struct Attachment
4545
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
4646
public async Task<MemoryStream> DownloadContentAsStreamAsync(WebUntisClient client, int timeout = 2000, CancellationToken ct = default)
4747
{
48-
string storageResponseString = await client.MakeAPIGetRequestAsync($"/WebUntis/api/rest/view/v1/messages/{Id}/attachmentstorageurl", ct);
48+
string storageResponseString = await client.MakeAPIGetRequestAsync($"/WebUntis/api/rest/view/v1/messages/{_id}/attachmentstorageurl", ct);
4949

5050
JObject data = JObject.Parse(storageResponseString);
5151
JArray headerArray = data.Value<JArray>("additionalHeaders");

WebUntisAPI.Client/Models/Messages/Draft.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class Draft
8383
/// The file attachments of the draft
8484
/// </summary>
8585
[JsonProperty("storageAttachments")]
86-
public List<Attachment> Attachments { get; set; }
86+
public List<Attachment> Attachments { get; set; } = new List<Attachment>();
8787

8888
/// <summary>
8989
/// The option for the recipient
@@ -92,7 +92,7 @@ public class Draft
9292
public string RecipientOption { get; set; }
9393

9494
/// <summary>
95-
/// Forbid the reply (you need the permission)
95+
/// Forbid the reply (you need the permission to do that)
9696
/// </summary>
9797
[JsonProperty("forbidReply")]
9898
public bool ForbidReply { get; set; }

WebUntisAPI.Client/Models/Messages/Message.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public class Message
9090
/// The file attachments of the message
9191
/// </summary>
9292
[JsonProperty("storageAttachments")]
93-
public List<Attachment> Attachments { get; set; }
93+
public List<Attachment> Attachments { get; set; } = new List<Attachment>();
9494

9595
/// <summary>
9696
/// Is this a report message

WebUntisAPI.Client/Models/Messages/MessagePerson.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ public class MessagePerson
4242
public Uri ImageUrl { get; set; } = null;
4343

4444
/// <summary>
45-
/// The user id of the user
45+
/// The id of the user
4646
/// </summary>
47-
public int UserId { get; set; }
47+
[JsonProperty("userId")]
48+
public int Id { get; set; }
4849
}
4950
}

WebUntisAPI.Client/Models/NewsMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class NewsMessage
2020
public string Subject { get; set; }
2121

2222
/// <summary>
23-
/// The normal text of the news in HTML
23+
/// The normal text of the news (<![CDATA[<br>]]> is used for line breaks)
2424
/// </summary>
2525
[JsonProperty("text")]
2626
public string Text { get; set; }

WebUntisAPI.Client/WebUntisClient.Messages.cs

Lines changed: 204 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Net.Http.Headers;
1212
using System.Net;
1313
using System.IO;
14-
using System.Runtime.InteropServices;
1514

1615
namespace WebUntisAPI.Client
1716
{
@@ -81,6 +80,205 @@ public async Task<MessagePreview[]> GetMessageInboxAsync(CancellationToken ct =
8180
return new JsonSerializer().Deserialize<List<MessagePreview>>(jsonMsg.CreateReader()).ToArray();
8281
}
8382

83+
/// <summary>
84+
/// Send a draft
85+
/// </summary>
86+
/// <param name="draft">The draft that you want to send</param>
87+
/// <param name="recipients">The recipients for the message</param>
88+
/// <param name="timeout">The time out for the attachment download (when the draft had attachments they must be downloaded to send them)</param>
89+
/// <param name="ct">Cancellation token</param>
90+
/// <returns>The preview of the sent message</returns>
91+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
92+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
93+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
94+
public async Task<MessagePreview> SendMessageAsync(Draft draft, MessagePerson[] recipients, int timeout = 2000, CancellationToken ct = default)
95+
{
96+
Tuple<string, MemoryStream>[] attachments = new Tuple<string, MemoryStream>[0];
97+
if (draft.Attachments.Count > 0)
98+
{
99+
Dictionary<string, Task<MemoryStream>> attachmentTasks = new Dictionary<string, Task<MemoryStream>>();
100+
101+
foreach (Attachment attachment in draft.Attachments)
102+
attachmentTasks.Add(attachment.Name, attachment.DownloadContentAsStreamAsync(this, timeout, ct));
103+
104+
await Task.WhenAll(attachmentTasks.Values);
105+
attachments = attachmentTasks.Select(attachment => new Tuple<string, MemoryStream>(attachment.Key, attachment.Value.Result)).ToArray();
106+
}
107+
108+
return await SendMessageAsync(draft.Subject, draft.Content, recipients, draft.ForbidReply, attachments, ct);
109+
}
110+
111+
/// <summary>
112+
/// Send a message
113+
/// </summary>
114+
/// <param name="subject">The subject</param>
115+
/// <param name="content">The content (use <![CDATA[<br>]]> for line breaks</param>
116+
/// <param name="recipients">The recipients for the message</param>
117+
/// <param name="forbidReply">Is a reply forbidden (it need a permission to to that)</param>
118+
/// <param name="attachments">The attachments to send (Item1 is the name and Item2 the content)</param>
119+
/// <param name="ct">Cancellation token</param>
120+
/// <returns>The preview of the sent message</returns>
121+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
122+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
123+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
124+
public async Task<MessagePreview> SendMessageAsync(string subject, string content, MessagePerson[] recipients, bool forbidReply, Tuple<string, MemoryStream>[] attachments = null, CancellationToken ct = default)
125+
{
126+
// Check for disposing
127+
if (_disposedValue)
128+
throw new ObjectDisposedException(GetType().FullName);
129+
130+
// Check if you logged in
131+
if (!LoggedIn)
132+
throw new UnauthorizedAccessException("You're not logged in");
133+
134+
MultipartFormDataContent requestContent = new MultipartFormDataContent();
135+
136+
// Json part
137+
StringWriter sw = new StringWriter();
138+
using (JsonWriter writer = new JsonTextWriter(sw))
139+
{
140+
writer.WriteStartObject();
141+
142+
writer.WritePropertyName("subject");
143+
writer.WriteValue(subject);
144+
145+
writer.WritePropertyName("content");
146+
writer.WriteValue(content);
147+
148+
writer.WritePropertyName("requestConfirmation");
149+
writer.WriteValue(false);
150+
151+
writer.WritePropertyName("recipientUserIds");
152+
writer.WriteStartArray();
153+
foreach (MessagePerson recipient in recipients)
154+
writer.WriteValue(recipient.Id);
155+
writer.WriteEndArray();
156+
157+
writer.WritePropertyName("oneDriveAttachments");
158+
writer.WriteStartArray();
159+
writer.WriteEndArray();
160+
161+
writer.WritePropertyName("forbidReply");
162+
writer.WriteValue(forbidReply);
163+
164+
writer.WriteEndObject();
165+
166+
StringContent jsonContent = new StringContent(sw.GetStringBuilder().ToString(), Encoding.UTF8, "application/json");
167+
requestContent.Add(jsonContent, "request", "blob");
168+
}
169+
170+
// Attachment part
171+
foreach (Tuple<string, MemoryStream> attachment in attachments)
172+
{
173+
ByteArrayContent fileContent = new ByteArrayContent(attachment.Item2.ToArray());
174+
fileContent.Headers.Add("Content-Type", "application/x-msdownload");
175+
requestContent.Add(fileContent, "attachments", attachment.Item1);
176+
}
177+
178+
HttpRequestMessage request = new HttpRequestMessage()
179+
{
180+
Method = HttpMethod.Post,
181+
RequestUri = new Uri(ServerUrl + "/WebUntis/api/rest/view/v2/messages/users"),
182+
Content = requestContent
183+
};
184+
request.Headers.Add("JSESSIONID", _sessonId);
185+
request.Headers.Add("schoolname", _schoolName);
186+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _bearerToken);
187+
188+
HttpResponseMessage response = await _client.SendAsync(request, ct);
189+
190+
// Verify response
191+
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
192+
{
193+
_ = LogoutAsync();
194+
throw new UnauthorizedAccessException("You're not logged in");
195+
}
196+
197+
if (response.StatusCode != HttpStatusCode.OK)
198+
throw new HttpRequestException($"There was an error while the http request (Code: {response.StatusCode}).");
199+
200+
return JsonConvert.DeserializeObject<MessagePreview>(await response.Content.ReadAsStringAsync());
201+
}
202+
203+
/// <summary>
204+
/// Revoke a message (move back into drafts)(only for self-sent messages!)
205+
/// </summary>
206+
/// <param name="message">The message to revoke</param>
207+
/// <param name="ct">Cancellation token</param>
208+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
209+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
210+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
211+
public async Task RevokeMessageAsync(MessagePreview message, CancellationToken ct = default)
212+
{
213+
await RevokeMessageAsync(new Message() { Id = message.Id }, ct);
214+
}
215+
216+
/// <summary>
217+
/// Revoke a message (move back into drafts)(only for self-sent messages!)
218+
/// </summary>
219+
/// <param name="message">The message to revoke</param>
220+
/// <param name="ct">Cancellation token</param>
221+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
222+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
223+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
224+
public async Task RevokeMessageAsync(Message message, CancellationToken ct = default)
225+
{
226+
// Check for disposing
227+
if (_disposedValue)
228+
throw new ObjectDisposedException(GetType().FullName);
229+
230+
// Check if you logged in
231+
if (!LoggedIn)
232+
throw new UnauthorizedAccessException("You're not logged in");
233+
234+
HttpRequestMessage request = new HttpRequestMessage()
235+
{
236+
Method = HttpMethod.Post,
237+
RequestUri = new Uri(ServerUrl + $"/WebUntis/api/rest/view/v1/messages/{message.Id}/revoke"),
238+
};
239+
request.Headers.Add("JSESSIONID", _sessonId);
240+
request.Headers.Add("schoolname", _schoolName);
241+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _bearerToken);
242+
243+
HttpResponseMessage response = await _client.SendAsync(request, ct);
244+
245+
// Verify response
246+
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
247+
{
248+
_ = LogoutAsync();
249+
throw new UnauthorizedAccessException("You're not logged in");
250+
}
251+
252+
if (response.StatusCode != HttpStatusCode.OK)
253+
throw new HttpRequestException($"There was an error while the http request (Code: {response.StatusCode}).");
254+
}
255+
256+
/// <summary>
257+
/// Delete a message
258+
/// </summary>
259+
/// <param name="message">Message to delete</param>
260+
/// <param name="ct">Cancellation token</param>
261+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
262+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
263+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
264+
public async Task DeleteMessageAsync(MessagePreview message, CancellationToken ct = default)
265+
{
266+
await DeleteDraftAsync(new Draft() { Id = message.Id }, ct);
267+
}
268+
269+
/// <summary>
270+
/// Delete a message
271+
/// </summary>
272+
/// <param name="message">Message to delete</param>
273+
/// <param name="ct">Cancellation token</param>
274+
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
275+
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
276+
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
277+
public async Task DeleteMessageAsync(Message message, CancellationToken ct = default)
278+
{
279+
await DeleteDraftAsync(new Draft() { Id = message.Id }, ct);
280+
}
281+
84282
/// <summary>
85283
/// Get all your saved drafts
86284
/// </summary>
@@ -111,7 +309,7 @@ public async Task<DraftPreview[]> GetSavedDraftsAsync(CancellationToken ct = def
111309
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
112310
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
113311
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
114-
public async Task<DraftPreview> CreateDraftAsync(string subject, string content, string recipientOption, bool forbidReply, bool copyToStudent, Tuple<string, MemoryStream>[] attachments, CancellationToken ct = default)
312+
public async Task<DraftPreview> CreateDraftAsync(string subject, string content, string recipientOption, bool forbidReply, bool copyToStudent, Tuple<string, MemoryStream>[] attachments = null, CancellationToken ct = default)
115313
{
116314
// Check for disposing
117315
if (_disposedValue)
@@ -197,13 +395,13 @@ public async Task<DraftPreview> CreateDraftAsync(string subject, string content,
197395
/// <remarks>Change all in the <paramref name="draft"/> excepted the attachments</remarks>
198396
/// <param name="draft">The draft to update</param>
199397
/// <param name="newAttachments">The new attachments (Item1 is the file name and Item2 the content)</param>
200-
/// <param name="attachmentIdsToDelete">The <see cref="Attachment.Id"/> from the attachment you want to delete</param>
398+
/// <param name="attachmentToDelete">The attachments from the draft you want to delete</param>
201399
/// <param name="ct">Cancellation token</param>
202400
/// <returns>The preview of the created draft</returns>
203401
/// <exception cref="ObjectDisposedException">Thrown when the instance was disposed</exception>
204402
/// <exception cref="UnauthorizedAccessException">Thrown when you're logged in</exception>
205403
/// <exception cref="HttpRequestException">Thrown when an error happened while the http request</exception>
206-
public async Task<DraftPreview> UpdateDraftAsync(Draft draft, Tuple<string, MemoryStream>[] newAttachments = null, string[] attachmentIdsToDelete = null, CancellationToken ct = default)
404+
public async Task<DraftPreview> UpdateDraftAsync(Draft draft, Tuple<string, MemoryStream>[] newAttachments = null, Attachment[] attachmentToDelete = null, CancellationToken ct = default)
207405
{
208406
// Check for disposing
209407
if (_disposedValue)
@@ -242,8 +440,8 @@ public async Task<DraftPreview> UpdateDraftAsync(Draft draft, Tuple<string, Memo
242440

243441
writer.WritePropertyName("attachmentIdsToDelete");
244442
writer.WriteStartArray();
245-
foreach (string attachmentId in attachmentIdsToDelete)
246-
writer.WriteValue(attachmentId);
443+
foreach (Attachment attachment in attachmentToDelete ?? new Attachment[0])
444+
writer.WriteValue(attachment._id);
247445
writer.WriteEndArray();
248446

249447
writer.WritePropertyName("forbidReply");

0 commit comments

Comments
 (0)