Skip to content

Commit

Permalink
Merge pull request #50 from lapulpeta/49-upload-media
Browse files Browse the repository at this point in the history
49 upload media
  • Loading branch information
lapulpeta authored Jan 26, 2023
2 parents 7d9f3f1 + 3e5dbb8 commit 249d3aa
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Nostrid.Core/Data/RelayService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class RelayService
{
private readonly string[] DefaultHighPriorityRelays = new[] { "wss://relay.damus.io", "wss://relay.nostr.info", "wss://nostr-pub.wellorder.net", "wss://nostr.onsats.org", "wss://nostr-pub.semisol.dev", "wss://nostr.walletofsatoshi", "wss://nostr-relay.wlvs.space", "wss://nostr.bitcoiner.social", "wss://nostr.zebedee.cloud", "wss://relay.nostr.ch", "wss://relay.nostr-latam.link" };
private readonly string[] DefaultLowPriorityRelays = new[] { "wss://nostr.openchain.fr", "wss://nostr.sandwich.farm", "wss://nostr.ono.re", "wss://nostr.rocks", "wss://nostr-relay.untethr.me", "wss://nostr.mom", "wss://relayer.fiatjaf.com", "wss://expensive-relay.fiatjaf.com", "wss://freedom-relay.herokuapp.com/ws", "wss://nostr-relay.freeberty.net", "wss://nostr.zaprite.io", "wss://nostr.delo.software", "wss://nostr-relay.untethr.me", "wss://nostr.semisol.dev", "wss://nostr-verified.wellorder.net", "wss://nostr.drss.io", "wss://nostr.unknown.place", "wss://nostr.oxtr.dev", "wss://relay.grunch.dev", "wss://relay.cynsar.foundation", "wss://nostr-2.zebedee.cloud", "wss://nostr-relay.digitalmob.ro", "wss://no.str.cr" };
private const int MinRelays = 6;
private const int MinRelays = 8;
private const int PriorityLowerBound = 0;
private const int PriorityHigherBound = 10;

Expand Down
11 changes: 11 additions & 0 deletions Nostrid.Core/Externals/IMediaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Nostrid.Externals
{
public interface IMediaService
{
public string Name { get; }

public int MaxSize { get; }

public Task<Uri?> UploadFile(Stream data, string filename, string mimeType);
}
}
22 changes: 22 additions & 0 deletions Nostrid.Core/Externals/MediaServiceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Nostrid.Externals
{
public class MediaServiceProvider
{
private List<IMediaService> _mediaServices = new();

public MediaServiceProvider(IEnumerable<IMediaService> mediaServices)
{
_mediaServices = new(mediaServices);
}

public void RegisterMediaService(IMediaService service)
{
_mediaServices.Add(service);
}

public IEnumerable<IMediaService> GetMediaServices()
{
return _mediaServices.AsReadOnly();
}
}
}
52 changes: 52 additions & 0 deletions Nostrid.Core/Externals/NostrBuildMediaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Net.Http.Headers;
using System.Text.RegularExpressions;

namespace Nostrid.Externals
{
public partial class NostrBuildMediaService : IMediaService
{
private static Regex _linkRegex = LinkRegex();

public string Name => "nostr.build";

public int MaxSize { get => 50 * 1024 * 1024; }

public async Task<Uri?> UploadFile(Stream data, string filename, string mimeType)
{
try
{
using var httpClient = new HttpClient();
using var httpContent = new MultipartFormDataContent();
using var fileContent = new StreamContent(data);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
httpContent.Add(fileContent, "fileToUpload", filename);
httpContent.Add(new StringContent("Upload Image"), "submit");
var response = await httpClient.PostAsync("https://nostr.build/upload.php", httpContent);
if (response.IsSuccessStatusCode)
{
var responseText = await response.Content.ReadAsStringAsync();
if (responseText.IsNotNullOrEmpty())
{
var match = _linkRegex.Match(responseText);
if (match.Success)
{
var link = match.Groups["link"].Value;
if (Uri.IsWellFormedUriString(link, UriKind.Absolute))
{
return new Uri(link);
}
}
}
}
}
catch (Exception ex)
{

}
return null;
}

[GeneratedRegex(".*(?<link>https://nostr.build/i/nostr.build_([0-9a-f]+).([0-9a-zA-Z]+)).*")]
private static partial Regex LinkRegex();
}
}
57 changes: 57 additions & 0 deletions Nostrid.Core/Externals/VoidCatMediaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.IO;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text.RegularExpressions;

namespace Nostrid.Externals
{
public partial class VoidCatMediaService : IMediaService
{
private static Regex _linkRegex = LinkRegex();

public string Name => "void.cat";

public int MaxSize { get => 50 * 1024 * 1024; }

public async Task<Uri?> UploadFile(Stream data, string filename, string mimeType)
{
try
{
var sha256 = Convert.ToHexString(SHA256.HashData(data));
data.Position = 0;

using var httpClient = new HttpClient();
using var fileContent = new StreamContent(data);
fileContent.Headers.Add("V-Full-Digest", sha256);
fileContent.Headers.Add("V-Filename", filename);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
var response = await httpClient.PostAsync("https://void.cat/upload?cli=true", fileContent);

if (response.IsSuccessStatusCode)
{
var responseText = await response.Content.ReadAsStringAsync();
if (responseText.IsNotNullOrEmpty())
{
var match = _linkRegex.Match(responseText);
if (match.Success)
{
var link = $"https://{match.Groups["link"].Value}";
if (Uri.IsWellFormedUriString(link, UriKind.Absolute))
{
return new Uri(link);
}
}
}
}
}
catch (Exception ex)
{

}
return null;
}

[GeneratedRegex("(http(s?):\\/\\/(?<link>[^ ]*))")]
private static partial Regex LinkRegex();
}
}
5 changes: 4 additions & 1 deletion Nostrid.Core/Shared/AsyncLink.razor
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ else
Audio = 3
}

private static string[] WellKnownHosts = new[]{ "https://void.cat" };

private MediaType mediaType = AsyncLink.MediaType.Other;
private string mediaTypeHeader = string.Empty;

Expand All @@ -70,7 +72,8 @@ else
{
try
{
using (var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, Url)))
var method = WellKnownHosts.Any(s => Url.StartsWith(s)) ? HttpMethod.Get : HttpMethod.Head;
using (var response = await client.SendAsync(new HttpRequestMessage(method, Url), HttpCompletionOption.ResponseHeadersRead))
{
if (!response.IsSuccessStatusCode)
{
Expand Down
59 changes: 48 additions & 11 deletions Nostrid.Core/Shared/NoteComposer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

@using Nostrid.Data.Relays;
@using Nostrid.Data;
@using Nostrid.Externals;
@using Nostrid.Misc;
@using Nostrid.Model;

@inject AccountService accountService
@inject FeedService feedService
@inject ConfigService configService
@inject MediaServiceProvider mediaServiceProvider

@code{
@code {
[Parameter]
public Event? ReplyTo { get; set; }

Expand Down Expand Up @@ -46,25 +48,42 @@
<DataAnnotationsValidator />
<ValidationSummary />
<textarea value="@textInput.Text" @oninput="@((args) => { textInput.Text = args.Value as string ?? string.Empty; })"
rows="4" style="resize: none;" class="form-control" placeholder="Leave a comment here" id="floatingTextarea" />
rows="4" style="resize: none;" class="form-control" placeholder="Leave a comment here" id="floatingTextarea" />
@if (sending)
{
<div class="d-flex flex-row align-items-center flex-wrap mt-2">
<button class="btn btn-secondary me-2" @onclick="@CancelPow">
<i class="bi bi-x"></i>
Cancel PoW
</button>
<span class="d-inline-block my-2">
<span>Sending with pow @configService.MainConfig.TargetDiffOutgoing.</span>
<span class="d-inline-block">If you leave this page it will be aborted.</span>
</span>
<button class="btn btn-secondary me-2" @onclick="@CancelPow">
<i class="bi bi-x"></i>
Cancel PoW
</button>
<span class="d-inline-block my-2">
<span>Sending with pow @configService.MainConfig.TargetDiffOutgoing.</span>
<span class="d-inline-block">If you leave this page it will be aborted.</span>
</span>
</div>
}
else
{
<button disabled="@string.IsNullOrEmpty(textInput.Text)" type="submit" class="btn btn-primary mt-2">
@(ReplyTo != null ? "Reply" : "Send note")
</button>
@if (mediaServiceProvider.GetMediaServices().Count() > 0)
{
<div class="btn-group ms-2">
<label class="btn btn-outline-secondary mt-2">
Upload file <InputFile class="d-none" OnChange="@LoadFile" />
</label>
<button type="button" class="btn btn-outline-secondary mt-2 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
@foreach (var service in mediaServiceProvider.GetMediaServices())
{
<li><a role="button" class="dropdown-item @(selectedService == service ? "active" : "")" @onclick="@(() => selectedService = service)">@service.Name</a></li>
}
</ul>
</div>
}
}
</EditForm>
</div>
Expand All @@ -78,7 +97,7 @@
private bool sending;

private CancellationTokenSource? powCancelSource;

private IMediaService? selectedService;

private void CancelPow()
{
Expand Down Expand Up @@ -116,6 +135,7 @@
protected override void OnInitialized()
{
accountService.MainAccountChanged += MainAccountChanged;
selectedService = mediaServiceProvider.GetMediaServices().FirstOrDefault();
}

private void MainAccountChanged(object? sender, EventArgs args)
Expand All @@ -126,6 +146,23 @@
});
}

private async Task LoadFile(InputFileChangeEventArgs e)
{
if (selectedService == null) return;

var file = e.File;
using var stream = file.OpenReadStream(selectedService.MaxSize);
using var memory = new MemoryStream();
await stream.CopyToAsync(memory);
memory.Position = 0;

var uri = await selectedService.UploadFile(memory, file.Name, file.ContentType);
if (uri != null)
{
textInput.Text += uri.ToString();
}
}

#region Dispose
private bool _disposed;

Expand Down
2 changes: 2 additions & 0 deletions Nostrid.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.JSInterop;
using Nostrid;
using Nostrid.Data;
using Nostrid.Externals;
using Nostrid.Interfaces;
using Nostrid.Web;
using Nostrid.Web.Services;
Expand All @@ -26,6 +27,7 @@
builder.Services.AddSingleton<ConfigService>();
builder.Services.AddSingleton<DatabaseService>();
builder.Services.AddSingleton<IClipboardService, ClipboardService>();
builder.Services.AddSingleton<MediaServiceProvider>();

var host = builder.Build();

Expand Down
7 changes: 7 additions & 0 deletions Nostrid.Web/libman.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
"js/bootstrap.js",
"js/bootstrap.min.js"
]
},
{
"library": "[email protected]",
"destination": "wwwroot/lib/popper.js/",
"files": [
"umd/popper.min.js"
]
}
]
}
1 change: 1 addition & 0 deletions Nostrid.Web/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="lib/popper.js/umd/popper.min.js"></script>
<script src="lib/bootstrap/js/bootstrap.min.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
</body>
Expand Down
6 changes: 6 additions & 0 deletions Nostrid.Web/wwwroot/lib/popper.js/umd/popper.min.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Nostrid/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Ganss.Xss;
using Nostrid.Data;
using Nostrid.Externals;
using Nostrid.Interfaces;
using Nostrid.Misc;
using Plugin.LocalNotification;
Expand Down Expand Up @@ -38,6 +39,9 @@ public static MauiApp CreateMauiApp()
builder.Services.AddSingleton<INotificationCounter, NotificationCounter>();
builder.Services.AddSingleton<ConfigService>();
builder.Services.AddSingleton<IClipboardService, ClipboardService>();
builder.Services.AddSingleton<MediaServiceProvider>();
builder.Services.AddSingleton<IMediaService, NostrBuildMediaService>();
builder.Services.AddSingleton<IMediaService, VoidCatMediaService>();

var app = builder.Build();

Expand Down
13 changes: 13 additions & 0 deletions Nostrid/libman.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"destination": "wwwroot/lib/popper.js/",
"files": [
"umd/popper.min.js"
]
}
]
}
1 change: 1 addition & 0 deletions Nostrid/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</div>

<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="lib/popper.js/umd/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>

</body>
Expand Down
6 changes: 6 additions & 0 deletions Nostrid/wwwroot/lib/popper.js/umd/popper.min.js

Large diffs are not rendered by default.

0 comments on commit 249d3aa

Please sign in to comment.