Skip to content

Commit

Permalink
setup a fake nostr service to generate random events
Browse files Browse the repository at this point in the history
  • Loading branch information
kfrancis committed May 24, 2023
1 parent 9b25cf4 commit a5a018b
Show file tree
Hide file tree
Showing 9 changed files with 614 additions and 388 deletions.
707 changes: 354 additions & 353 deletions .gitignore

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions All.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B82E3B66-170C-4AE0-8591-B03CE3B49C6C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Bogus.Premium.LicenseKey = Bogus.Premium.LicenseKey
.github\workflows\main.yml = .github\workflows\main.yml
README.md = README.md
EndProjectSection
Expand Down
2 changes: 2 additions & 0 deletions src/NuSocial/GlobalSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public User CurrentUser
}
}

public bool DemoMode { get; set; }

private static void UpdateUser(NostrPublicKey publicKey, NostrPrivateKey? privateKey)
{
if (privateKey != null)
Expand Down
5 changes: 5 additions & 0 deletions src/NuSocial/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ private static ContainerBuilder GetAutofacContainerBuilder(IServiceCollection se
var db = new LocalStorage();
services.AddSingleton<IDatabase>(db);
services.AddSingleton<ICustomDispatcher, MauiDispatcher>();

#if DEBUG
services.AddSingleton<INostrService>(new TestNostrService());
#else
services.AddSingleton<INostrService>(new NostrService(db));
#endif
services.AddLocalization();
services.AddLogging(logging => logging.AddSerilog());

Expand Down
5 changes: 5 additions & 0 deletions src/NuSocial/NuSocial.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
<ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BindableProps" Version="1.3.7" />
<PackageReference Include="Bogus" Version="34.0.2" />
<PackageReference Include="Bogus.Tools.Analyzer" Version="34.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Maui" Version="5.1.0" />
<PackageReference Include="CommunityToolkit.Maui.Markup" Version="3.1.0" />
<PackageReference Include="CommunityToolkit.MVVM" Version="8.2.0" />
Expand Down
184 changes: 183 additions & 1 deletion src/NuSocial/Services/NostrService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.Messaging;
using Bogus;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging.Abstractions;
using Nostr.Client.Client;
using Nostr.Client.Communicator;
Expand Down Expand Up @@ -403,4 +404,185 @@ private Task<NostrMetadataEvent> GetFollowingInfoAsync(string publicKey, Cancell
throw new NotImplementedException();
}
}

public class TestNostrService : INostrService, IDisposable
{
private bool _isDisposed;
private readonly Faker<NostrEvent> _nostrFaker;
private readonly Faker<Profile> _profileFaker;
private Timer? _timer;

public TestNostrService()
{
var faker = new Faker();
List<NostrKeyPair> authorPool = new List<NostrKeyPair>();
for (int i = 0; i < faker.Random.Int(10, 100); i++)
{
var keyPair = NostrKeyPair.GenerateNew();
authorPool.Add(keyPair);
}
_nostrFaker = new Faker<NostrEvent>()
.RuleFor(n => n.Pubkey, f => authorPool[f.UniqueIndex % authorPool.Count].PublicKey.Bech32)
.RuleFor(n => n.CreatedAt, f => f.Date.Recent(f.Random.Int(1, 7)))
.RuleFor(n => n.Kind, f => NostrKind.ShortTextNote)
.RuleFor(n => n.Tags, f => default)
.RuleFor(n => n.Content, f => TestNostrService.GenerateRandomMarkdownContent(f))
.RuleFor(n => n.Id, (f, n) => n.ComputeId())
.RuleFor(n => n.Sig, (f, n) =>
{
var index = f.UniqueIndex % authorPool.Count;
var keyPair = authorPool[index];
var sig = n.ComputeSignature(keyPair.PrivateKey);
return sig;
})
.FinishWith((f, e) =>
{
Console.WriteLine("Content event generated! Id={0}", e.Id);
});

_profileFaker = new Faker<Profile>()
.RuleFor(p => p.Name, f => f.Internet.UserName())
.RuleFor(p => p.DisplayName, f => f.Random.Bool(0.2f) ? f.Internet.UserName() : null)
.RuleFor(p => p.Picture, f => f.Random.Bool(0.8f) ? f.Internet.Avatar() : null)
.RuleFor(p => p.Relays, f => default);
}

private static string GenerateRandomMarkdownContent(Faker faker)
{
var markdownContent = $"{string.Join(" ", faker.Lorem.Words(faker.Random.Int(1,3)))}\n\n";
var hasSomethingInteresting = false;
if (!hasSomethingInteresting && faker.Random.Bool(0.05f))
{
for (var i = 0; i < faker.Random.Int(1, 3); i++)
{
markdownContent += $"{faker.Lorem.Paragraph()}\n\n";
}
hasSomethingInteresting = true;
}

// Add a list
if (!hasSomethingInteresting && faker.Random.Bool(0.05f))
{
markdownContent += "## List\n\n";
for (var i = 0; i < faker.Random.Int(2, 5); i++)
{
markdownContent += $"* {faker.Lorem.Sentence()}\n";
}
hasSomethingInteresting = true;
if (faker.Random.Bool(0.05f))
{
hasSomethingInteresting = false;
}
}

// Add a quote
if (!hasSomethingInteresting && faker.Random.Bool(0.05f))
{
markdownContent += "\n> " + faker.Lorem.Sentence() + "\n\n";
hasSomethingInteresting = true;
if (faker.Random.Bool(0.05f))
{
hasSomethingInteresting = false;
}
}

// Add a code block
if (!hasSomethingInteresting && faker.Random.Bool(0.05f))
{
markdownContent += "```\n" + faker.Lorem.Sentence() + "\n```\n\n";
hasSomethingInteresting = true;
if (faker.Random.Bool(0.05f))
{
hasSomethingInteresting = false;
}
}

// Add a table
if (!hasSomethingInteresting && faker.Random.Bool(0.05f))
{
markdownContent += "| Column 1 | Column 2 |\n| -------- | -------- |\n";
for (var i = 0; i < faker.Random.Int(2, 5); i++)
{
markdownContent += $"| {faker.Lorem.Word()} | {faker.Lorem.Word()} |\n";
}
hasSomethingInteresting = true;
if (faker.Random.Bool(0.05f))
{
hasSomethingInteresting = false;
}
}

// Add an emoji
if (!hasSomethingInteresting && faker.Random.Bool(0.30f))
{
var emojis = new[] { "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇" };
markdownContent += "\n" + faker.PickRandom(emojis) + "\n";
hasSomethingInteresting = true;
if (faker.Random.Bool(0.05f))
{
hasSomethingInteresting = false;
}
}

return markdownContent;
}

public NostrClientStreams? Streams => throw new NotImplementedException();


public Task<Profile> GetProfileAsync(string publicKey, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(_profileFaker.Generate());
}

public void RegisterFilter(string subscription, NostrFilter filter)
{
// Do nothing
}

public void StartNostr()
{
var faker = new Faker();
_timer = new Timer(SendNostrPostMessage, null, 0, faker.Random.Int(1000, 5000));
}

private void SendNostrPostMessage(object? state)
{
var nostrEvent = _nostrFaker.Generate();
var message = new NostrPostMessage(nostrEvent);
WeakReferenceMessenger.Default.Send(message);
}

public void StopNostr()
{
_timer?.Dispose();
}

protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
_timer?.Dispose();
}

_isDisposed = true;
}
}

~TestNostrService()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
14 changes: 14 additions & 0 deletions src/NuSocial/ViewModels/LoginViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ public LoginViewModel(IDialogService dialogService,
}

public bool IsAccountKeyValid => !string.IsNullOrEmpty(AccountKey);
[RelayCommand(CanExecute = nameof(IsNotBusy))]
private Task DemoAsync()
{
return SetBusyAsync(async () =>
{
var keyPair = NostrKeyPair.GenerateNew();
var user = new User() { PublicKey = keyPair.PublicKey, PrivateKey = keyPair.PrivateKey };
await _db.UpdateUsersAsync(new ObservableCollection<User> { user });
GlobalSetting.Instance.DemoMode = true;
GlobalSetting.Instance.CurrentUser = user;
await Navigation.NavigateTo("//main", user);
});
}

[RelayCommand(CanExecute = nameof(IsNotBusy))]
private Task LoginAsync()
Expand Down
10 changes: 9 additions & 1 deletion src/NuSocial/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace NuSocial.ViewModels;
public partial class MainViewModel : BaseViewModel, ITransientDependency, IDisposable
{
private readonly IAuthorService _authorService;
private readonly INostrService _nostrService;
private CancellationTokenSource? _cts = new();
private const int _postThreshold = 100;

Expand All @@ -26,16 +27,23 @@ public partial class MainViewModel : BaseViewModel, ITransientDependency, IDispo

public MainViewModel(IDialogService dialogService,
INavigationService navigationService,
IAuthorService authorService)
IAuthorService authorService,
INostrService nostrService)
: base(dialogService, navigationService)
{
_authorService = authorService;
_nostrService = nostrService;
}

public string UnreadLabel => $"{L["Unread"]} ({_postsWaiting.Count})";

public override Task OnFirstAppear()
{
if (GlobalSetting.Instance.DemoMode && _nostrService is TestNostrService)
{
_nostrService.StartNostr();
}

WeakReferenceMessenger.Default.Send<ResetNavMessage>(new());

WeakReferenceMessenger.Default.Register<NostrUserChangedMessage>(this, (r, m) =>
Expand Down
74 changes: 41 additions & 33 deletions src/NuSocial/Views/LoginView.xaml
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<base:ContentPageFormBase
x:Class="NuSocial.Views.LoginView"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:base="clr-namespace:NuSocial.Core.View"
xmlns:loc="clr-namespace:NuSocial.Helpers"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:NuSocial.ViewModels"
Title="{Binding Title}"
Padding="0,20"
x:DataType="viewModels:LoginViewModel"
x:TypeArguments="viewModels:LoginViewModel"
Shell.FlyoutBehavior="Disabled">
<Grid
Margin="20,0"
RowDefinitions="Auto,Auto,*"
RowSpacing="10">
<Label Text="Enter your account key to login:" VerticalOptions="Center" />
<Entry
Grid.Row="1"
IsPassword="True"
Placeholder="{loc:Translate AccountKeyPlaceholder}"
Text="{Binding AccountKey}" />
<Button
Grid.Row="2"
Margin="0,10"
Command="{Binding LoginCommand}"
MaximumWidthRequest="200"
Style="{StaticResource PrimaryActionButtonStyle}"
Text="{loc:Translate Login}"
VerticalOptions="Start" />
</Grid>
<?xml version="1.0" encoding="utf-8" ?>
<base:ContentPageFormBase
x:Class="NuSocial.Views.LoginView"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:base="clr-namespace:NuSocial.Core.View"
xmlns:loc="clr-namespace:NuSocial.Helpers"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:NuSocial.ViewModels"
Title="{Binding Title}"
Padding="0,20"
x:DataType="viewModels:LoginViewModel"
x:TypeArguments="viewModels:LoginViewModel"
Shell.FlyoutBehavior="Disabled">
<Grid
Margin="20,0"
RowDefinitions="Auto,Auto,*,*"
RowSpacing="10">
<Label Text="Enter your account key to login:" VerticalOptions="Center" />
<Entry
Grid.Row="1"
IsPassword="True"
Placeholder="{loc:Translate AccountKeyPlaceholder}"
Text="{Binding AccountKey}" />
<Button
Grid.Row="2"
Margin="0,10"
Command="{Binding LoginCommand}"
MaximumWidthRequest="200"
Style="{StaticResource PrimaryActionButtonStyle}"
Text="{loc:Translate Login}"
VerticalOptions="Start" />
<Button
Grid.Row="3"
Margin="0,10"
Command="{Binding DemoCommand}"
MaximumWidthRequest="200"
Style="{StaticResource PrimaryActionButtonStyle}"
Text="{loc:Translate Demo}"
VerticalOptions="Start" />
</Grid>
</base:ContentPageFormBase>

0 comments on commit a5a018b

Please sign in to comment.