login to the app

This commit is contained in:
kawaiizenbo 2023-02-04 23:45:16 -07:00
parent 35f6d47a5f
commit 204cef09aa
12 changed files with 404 additions and 7 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 KawaiiZenbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

10
README.md Normal file
View file

@ -0,0 +1,10 @@
# WMstodon
Mastodon client for Windows 10 Mobile made without API Libraries
## Requirements
- Windows 10 Creators Update (version 1703) or later (or Windows 11)
The app is intended to be used on Windows Mobile but will work on desktop as well
### For Building
- Visual Studio 2017 (last version for building Windows Mobile apps)
- Windows 10 SDK 10.0.15063

View file

@ -66,7 +66,7 @@ namespace WMstodon
// When the navigation stack isn't restored navigate to the first page, // When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation // configuring the new page by passing required information as a navigation
// parameter // parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments); rootFrame.Navigate(typeof(SelectInstancePage), e.Arguments);
} }
// Ensure the current window is active // Ensure the current window is active
Window.Current.Activate(); Window.Current.Activate();

54
WMstodon/HTTPUtils.cs Normal file
View file

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Windows.Storage;
namespace WMstodon
{
class HTTPUtils
{
static ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
// still usin em
public static async Task<KeyValuePair<HttpStatusCode, string>> GETAsync(string URL)
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri((string)localSettings.Values["instanceURL"]);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string)localSettings.Values["accessToken"]);
HttpResponseMessage response = await httpClient.GetAsync(URL);
httpClient.Dispose();
return new KeyValuePair<HttpStatusCode, string>(response.StatusCode, await response.Content.ReadAsStringAsync());
}
public static async Task<KeyValuePair<HttpStatusCode, string>> GETGenericAsync(string URL)
{
HttpClient generic = new HttpClient();
generic.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string)localSettings.Values["accessToken"]);
HttpResponseMessage response = await generic.GetAsync(URL);
generic.Dispose();
return new KeyValuePair<HttpStatusCode, string>(response.StatusCode, await response.Content.ReadAsStringAsync());
}
public static async Task<KeyValuePair<HttpStatusCode, string>> POSTAsync(string URL, HttpContent data)
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri((string)localSettings.Values["instanceURL"]);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string)localSettings.Values["accessToken"]);
HttpResponseMessage response = await httpClient.PostAsync(URL, data);
httpClient.Dispose();
return new KeyValuePair<HttpStatusCode, string>(response.StatusCode, await response.Content.ReadAsStringAsync());
}
public static async Task<KeyValuePair<HttpStatusCode, string>> POSTGenericAsync(string URL, HttpContent data)
{
HttpClient generic = new HttpClient();
generic.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string)localSettings.Values["accessToken"]);
HttpResponseMessage response = await generic.PostAsync(URL, data);
generic.Dispose();
return new KeyValuePair<HttpStatusCode, string>(response.StatusCode, await response.Content.ReadAsStringAsync());
}
}
}

111
WMstodon/JSONClasses.cs Normal file
View file

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WMstodon
{
public class Account
{
public string id { get; set; }
public string username { get; set; }
public string acct { get; set; }
public string display_name { get; set; }
public bool locked { get; set; }
public bool bot { get; set; }
public bool discoverable { get; set; }
public bool group { get; set; }
public DateTime created_at { get; set; }
public string note { get; set; }
public string url { get; set; }
public string avatar { get; set; }
public string avatar_static { get; set; }
public string header { get; set; }
public string header_static { get; set; }
public int followers_count { get; set; }
public int following_count { get; set; }
public int statuses_count { get; set; }
public object last_status_at { get; set; }
public object source { get; set; }
public object[] emojis { get; set; }
public object[] fields { get; set; }
}
public class Attachment
{
public string id { get; set; }
public string type { get; set; }
public string url { get; set; }
public string preview_url { get; set; }
public string remote_url { get; set; }
public string text_url { get; set; }
public AttachmentMeta meta { get; set; }
public string description { get; set; }
public string blurhash { get; set; }
}
public class AttachmentMeta
{
public AttachmentFocus focus { get; set; }
public AttachmentSizing original { get; set; }
public AttachmentSizing small { get; set; }
}
public class AttachmentFocus
{
public float x { get; set; }
public float y { get; set; }
}
public class AttachmentSizing
{
public int width { get; set; }
public int height { get; set; }
public string size { get; set; }
public float aspect { get; set; }
}
public class Status
{
public string id { get; set; }
public DateTime created_at { get; set; }
public object in_reply_to_id { get; set; }
public object in_reply_to_account_id { get; set; }
public bool sensitive { get; set; }
public string spoiler_text { get; set; }
public string visibility { get; set; }
public string language { get; set; }
public string uri { get; set; }
public string url { get; set; }
public int replies_count { get; set; }
public int reblogs_count { get; set; }
public int favourites_count { get; set; }
public bool favourited { get; set; }
public bool reblogged { get; set; }
public bool muted { get; set; }
public bool bookmarked { get; set; }
public string content { get; set; }
public object reblog { get; set; }
public MApplication application { get; set; }
public Account account { get; set; }
public Attachment[] media_attachments { get; set; }
public object[] mentions { get; set; }
public object[] tags { get; set; }
public object[] emojis { get; set; }
public object card { get; set; }
public object poll { get; set; }
}
public class Feed
{
public Status[] statuses { get; set; }
}
public class MApplication
{
public string name { get; set; }
public string website { get; set; }
}
}

21
WMstodon/LoginPage.xaml Normal file
View file

@ -0,0 +1,21 @@
<Page
x:Class="WMstodon.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WMstodon"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<TextBox x:Name="AuthCodeTextBox" Margin="10,0" TextWrapping="Wrap" Text="" VerticalAlignment="Center" PlaceholderText="Auth Code"/>
<Button x:Name="OpenAuthPageButton" Content="Open auth page in browser" HorizontalAlignment="Stretch" Margin="10,0,10,75" VerticalAlignment="Center" Click="OpenAuthPageButton_Click"/>
<Button x:Name="LogInButton" Content="Log In" HorizontalAlignment="Right" Margin="0,75,10,0" VerticalAlignment="Center" Width="75" Click="LogInButton_Click"/>
<TextBlock HorizontalAlignment="Center" Margin="0,0,0,300" TextWrapping="Wrap" Text="Placeholder Text" VerticalAlignment="Center"/>
<Button x:Name="BackButton" Content="Back" HorizontalAlignment="Left" Margin="10,75,0,0" VerticalAlignment="Center" Width="75" Click="BackButton_Click"/>
<TextBlock x:Name="ErrorTextBlock" HorizontalAlignment="Left" Margin="10,150,10,0" TextWrapping="Wrap" Text="" VerticalAlignment="Center" FocusVisualPrimaryBrush="Red" Foreground="Red"/>
<TextBlock x:Name="InstanceTextBlock" HorizontalAlignment="Left" Margin="10,0,0,133" TextWrapping="Wrap" Text="Instance:" VerticalAlignment="Center" FontSize="12"/>
</Grid>
</Page>

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace WMstodon
{
public sealed partial class LoginPage : Page
{
JObject passedArgument;
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
public LoginPage()
{
this.InitializeComponent();
}
private async void OpenAuthPageButton_Click(object sender, RoutedEventArgs e)
{
await Windows.System.Launcher.LaunchUriAsync(new Uri(localSettings.Values["instanceURL"] + "/oauth/authorize" +
"?client_id=" + passedArgument["client_id"] + "" +
"&scope=read+write+push" +
"&redirect_uri=urn:ietf:wg:oauth:2.0:oob" +
"&response_type=code"
));
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
passedArgument = (JObject)e.Parameter;
InstanceTextBlock.Text = (string)localSettings.Values["instanceURL"];
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
Frame.GoBack();
}
private async void LogInButton_Click(object sender, RoutedEventArgs e)
{
Dictionary<string, string> postopts = new Dictionary<string, string>();
postopts["client_id"] = (string)passedArgument["client_id"];
postopts["client_secret"] = (string)passedArgument["client_secret"];
postopts["redirect_uri"] = "urn:ietf:wg:oauth:2.0:oob";
postopts["grant_type"] = "authorization_code";
postopts["code"] = AuthCodeTextBox.Text;
postopts["scope"] = "read write push";
KeyValuePair<HttpStatusCode, string> response =
await HTTPUtils.POSTAsync("/oauth/token", new FormUrlEncodedContent(postopts));
if (response.Key == HttpStatusCode.OK)
{
JObject appResponseObj = JObject.Parse(response.Value);
localSettings.Values["accessToken"] = (string)appResponseObj["access_token"];
if ((await HTTPUtils.GETAsync("/api/v1/accounts/verify_credentials")).Key != HttpStatusCode.OK)
{
ErrorTextBlock.Text = "Could not log into service";
return;
}
Frame.Navigate(typeof(MainPage), appResponseObj);
}
else
{
ErrorTextBlock.Text = "Could not log into service:\n" + response.Key;
}
}
}
}

View file

@ -9,6 +9,9 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid> <Grid>
<Image x:Name="myAvatar" HorizontalAlignment="Left" Height="40" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="40"/>
<TextBlock x:Name="DisplayNameTextBlock" HorizontalAlignment="Left" Margin="60,0,0,30" TextWrapping="Wrap" Text="Loading..." VerticalAlignment="Bottom"/>
<TextBlock x:Name="UsernameTextBlock" HorizontalAlignment="Left" Margin="60,0,0,15" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Bottom" FontSize="12" Foreground="#FF777777"/>
</Grid> </Grid>
</Page> </Page>

View file

@ -3,28 +3,46 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using Newtonsoft.Json;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace WMstodon namespace WMstodon
{ {
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page public sealed partial class MainPage : Page
{ {
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
Account myAccount;
public MainPage() public MainPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
} }
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
string accountJSON = (await HTTPUtils.GETAsync("/api/v1/accounts/verify_credentials")).Value;
myAccount = JsonConvert.DeserializeObject<Account>(accountJSON);
DisplayNameTextBlock.Text = $"{myAccount.display_name}";
UsernameTextBlock.Text = $"@{myAccount.username}@{new Uri((string)localSettings.Values["instanceURL"]).DnsSafeHost}";
BitmapImage bitmap = new BitmapImage();
bitmap.UriSource = new Uri(myAccount.avatar_static);
myAvatar.Source = bitmap;
//await LoadFeed();
}
} }
} }

View file

@ -0,0 +1,18 @@
<Page
x:Class="WMstodon.SelectInstancePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WMstodon"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Loaded="Page_Loaded">
<Grid>
<TextBox x:Name="InstanceURLTextBox" Margin="10,0" TextWrapping="Wrap" Text="" VerticalAlignment="Center" PlaceholderText="Enter your Instance URL"/>
<Button x:Name="NextButton" Content="Next" HorizontalAlignment="Right" Margin="0,75,10,0" VerticalAlignment="Center" Width="75" Click="NextButton_Click"/>
<TextBlock x:Name="GreetingTextBlock" HorizontalAlignment="Center" Margin="0,0,0,200" TextWrapping="Wrap" Text="How do you do?" VerticalAlignment="Center" FontSize="24"/>
<TextBlock x:Name="ErrorTextBlock" HorizontalAlignment="Left" Margin="10,150,10,0" TextWrapping="Wrap" Text="" VerticalAlignment="Center" FocusVisualPrimaryBrush="Red" Foreground="Red"/>
</Grid>
</Page>

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace WMstodon
{
public sealed partial class SelectInstancePage : Page
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
public SelectInstancePage()
{
this.InitializeComponent();
}
private async void NextButton_Click(object sender, RoutedEventArgs e)
{
Dictionary<string, string> postopts = new Dictionary<string, string>();
postopts["client_name"] = "WMStodon";
postopts["redirect_uris"] = "urn:ietf:wg:oauth:2.0:oob";
postopts["scopes"] = "read write push";
postopts["website"] = "https://github.com/kawaiizenbo/WMStodon";
KeyValuePair<HttpStatusCode, string> response =
await HTTPUtils.POSTGenericAsync(InstanceURLTextBox.Text + "/api/v1/apps", new FormUrlEncodedContent(postopts));
if (response.Key == HttpStatusCode.OK)
{
JObject appResponseObj = JObject.Parse(response.Value);
localSettings.Values["instanceURL"] = InstanceURLTextBox.Text;
Frame.Navigate(typeof(LoginPage), appResponseObj);
}
else ErrorTextBlock.Text = "Could not create application on instance:\n" + response.Key;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
if (localSettings.Values["instanceURL"] != null && localSettings.Values["accessToken"] != null) Frame.Navigate(typeof(MainPage), null);
}
}
}

View file

@ -119,10 +119,18 @@
<Compile Include="App.xaml.cs"> <Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="HTTPUtils.cs" />
<Compile Include="JSONClasses.cs" />
<Compile Include="LoginPage.xaml.cs">
<DependentUpon>LoginPage.xaml</DependentUpon>
</Compile>
<Compile Include="MainPage.xaml.cs"> <Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon> <DependentUpon>MainPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SelectInstancePage.xaml.cs">
<DependentUpon>SelectInstancePage.xaml</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AppxManifest Include="Package.appxmanifest"> <AppxManifest Include="Package.appxmanifest">
@ -145,14 +153,25 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</ApplicationDefinition> </ApplicationDefinition>
<Page Include="LoginPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="MainPage.xaml"> <Page Include="MainPage.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="SelectInstancePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.8</Version> <Version>6.2.14</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.2</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">