Inital Commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
################################################################################
|
||||||
|
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
/TwitchDesktopNotifications/bin
|
||||||
|
/TwitchDesktopNotifications/obj
|
||||||
|
/.vs
|
||||||
|
/TwitchDesktopNotifications/App.config
|
||||||
30
Readme.md
Normal file
30
Readme.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Twitch Notify
|
||||||
|
## not affiliated with Twitch or Amazon
|
||||||
|
|
||||||
|
[](https://travis-ci.org/joemccann/dillinger)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Twitch Notify Requires [.NET 6 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.13-windows-x64-installer).
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Want to contribute? Great!
|
||||||
|
|
||||||
|
Project is built using Visual Studios 2022, must have Windows 10.0.17763 SDK installed
|
||||||
|
|
||||||
|
You must create a `App.config` file inside `TwitchDesktopNotifications` project.
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<add key="TwitchClientID" value="" />
|
||||||
|
<add key="TwitchClientSecret" value="" />
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
**Free Software, Hell Yeah!**
|
||||||
25
TwitchDesktopNotifications.sln
Normal file
25
TwitchDesktopNotifications.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33205.214
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwitchDesktopNotifications", "TwitchDesktopNotifications\TwitchDesktopNotifications.csproj", "{323F3EBC-C1B1-4DDE-9069-5A65EDA4EB16}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{323F3EBC-C1B1-4DDE-9069-5A65EDA4EB16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{323F3EBC-C1B1-4DDE-9069-5A65EDA4EB16}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{323F3EBC-C1B1-4DDE-9069-5A65EDA4EB16}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{323F3EBC-C1B1-4DDE-9069-5A65EDA4EB16}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {FE540A21-E905-4833-8D6A-01F7B31156C7}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
BIN
TwitchDesktopNotifications/Assets/icon.ico
Normal file
BIN
TwitchDesktopNotifications/Assets/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
TwitchDesktopNotifications/Assets/twitch.png
Normal file
BIN
TwitchDesktopNotifications/Assets/twitch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 499 B |
85
TwitchDesktopNotifications/Core/Notification.cs
Normal file
85
TwitchDesktopNotifications/Core/Notification.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.Core
|
||||||
|
{
|
||||||
|
internal class Notification
|
||||||
|
{
|
||||||
|
private Notification() {
|
||||||
|
ToastNotificationManagerCompat.OnActivated += toastArgs =>
|
||||||
|
{
|
||||||
|
// Obtain the arguments from the notification
|
||||||
|
ToastArguments args = ToastArguments.Parse(toastArgs.Argument);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process myProcess = new Process();
|
||||||
|
myProcess.StartInfo.UseShellExecute = true;
|
||||||
|
myProcess.StartInfo.FileName = args["streamerUrl"];
|
||||||
|
myProcess.Start();
|
||||||
|
}catch(Exception ex) { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Notification Instance;
|
||||||
|
|
||||||
|
public static Notification GetInstance()
|
||||||
|
{
|
||||||
|
if(Instance == null)
|
||||||
|
{
|
||||||
|
Instance = new Notification();
|
||||||
|
}
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendNotification(String streamerName, String streamerUrl, String profilePic, String streamThumbnail, String title)
|
||||||
|
{
|
||||||
|
String FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TwitchNotify");
|
||||||
|
|
||||||
|
streamThumbnail = streamThumbnail.Replace("{width}", 260.ToString()).Replace("{height}", 147.ToString());
|
||||||
|
|
||||||
|
// download there profile picture
|
||||||
|
string fileNameProfilePic = profilePic.Split("/").Last();
|
||||||
|
(new WebClient()).DownloadFile(new Uri(profilePic), FilePath+"/"+ fileNameProfilePic);
|
||||||
|
|
||||||
|
// download there profile picture
|
||||||
|
string fileNameThumbnailPic = streamThumbnail.Split("/").Last();
|
||||||
|
(new WebClient()).DownloadFile(new Uri(streamThumbnail),
|
||||||
|
FilePath + "/" + fileNameThumbnailPic
|
||||||
|
);
|
||||||
|
|
||||||
|
var builder = new ToastContentBuilder()
|
||||||
|
.AddArgument("streamerUrl", streamerUrl)
|
||||||
|
.AddText(streamerName + " is now live on Twitch")
|
||||||
|
.AddHeroImage(new Uri("file://" + (FilePath + "/" + fileNameThumbnailPic).Replace("\\", "/")))
|
||||||
|
.AddAppLogoOverride(new Uri("file://" + (FilePath + "/" + fileNameProfilePic).Replace("\\", "/")), ToastGenericAppLogoCrop.Circle)
|
||||||
|
.AddButton(new ToastButton()
|
||||||
|
.SetContent("Watch " + streamerName)
|
||||||
|
.AddArgument("action", "watch")
|
||||||
|
.SetBackgroundActivation())
|
||||||
|
.AddButton(new ToastButton()
|
||||||
|
.SetContent("Dismiss")
|
||||||
|
.AddArgument("action", "nothing")
|
||||||
|
.SetBackgroundActivation());
|
||||||
|
|
||||||
|
if(title != "") {
|
||||||
|
builder.AddText(title);
|
||||||
|
}
|
||||||
|
builder.Show(toast =>
|
||||||
|
{
|
||||||
|
toast.ExpirationTime = DateTime.Now.AddSeconds(15);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
212
TwitchDesktopNotifications/Core/TwitchFetcher.cs
Normal file
212
TwitchDesktopNotifications/Core/TwitchFetcher.cs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
using ABI.System;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using TwitchDesktopNotifications.JsonStructure;
|
||||||
|
using TwitchDesktopNotifications.JsonStructure.Helix;
|
||||||
|
using Windows.ApplicationModel.Background;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.Core
|
||||||
|
{
|
||||||
|
internal class TwitchFetcher
|
||||||
|
{
|
||||||
|
private TwitchFetcher() { }
|
||||||
|
|
||||||
|
public static TwitchFetcher instance { get; private set; }
|
||||||
|
|
||||||
|
string TwitchClientID = ConfigurationManager.AppSettings["TwitchClientID"];
|
||||||
|
string TwitchClientSecret = ConfigurationManager.AppSettings["TwitchClientSecret"];
|
||||||
|
|
||||||
|
List <StreamsData> currentlyLive = null;
|
||||||
|
|
||||||
|
public string guid { get; private set; }
|
||||||
|
|
||||||
|
public static TwitchFetcher GetInstance()
|
||||||
|
{
|
||||||
|
if(instance == null)
|
||||||
|
{
|
||||||
|
instance = new TwitchFetcher();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] buildPostData(Dictionary<string, string> postData)
|
||||||
|
{
|
||||||
|
string content = "";
|
||||||
|
foreach (var pair in postData)
|
||||||
|
{
|
||||||
|
content += HttpUtility.UrlEncode(pair.Key) + "=" + HttpUtility.UrlEncode(pair.Value) + "&";
|
||||||
|
}
|
||||||
|
content = content.TrimEnd('&');
|
||||||
|
return Encoding.UTF8.GetBytes(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T MakeRequest<T>(string endpoint)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (DataStore.GetInstance().Store.Authentication.ExpiresAsDate <= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
WebRequest request = WebRequest.Create("https://api.twitch.tv/" + endpoint);
|
||||||
|
request.Method = "GET";
|
||||||
|
request.Headers[HttpRequestHeader.Authorization] = String.Format("Bearer {0}", DataStore.GetInstance().Store.Authentication.AccessToken);
|
||||||
|
request.Headers["Client-ID"] = TwitchClientID;
|
||||||
|
WebResponse response = request.GetResponse();
|
||||||
|
Stream dataStream = response.GetResponseStream();
|
||||||
|
StreamReader reader = new StreamReader(dataStream);
|
||||||
|
string responseFromServer = reader.ReadToEnd();
|
||||||
|
reader.Close();
|
||||||
|
dataStream.Close();
|
||||||
|
response.Close();
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(responseFromServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FetchCurrentUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DataStore.GetInstance().Store.UserData = MakeRequest<User>("helix/users").Data[0];
|
||||||
|
DataStore.GetInstance().Save();
|
||||||
|
}catch(System.Exception ex)
|
||||||
|
{
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserData FetchUserData(string user_id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return MakeRequest<User>("helix/users?id=" + user_id).Data[0];
|
||||||
|
}catch(System.Exception ex)
|
||||||
|
{
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetLiveFollowingUsers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool isFinished = false;
|
||||||
|
if (DataStore.GetInstance().Store.UserData == null)
|
||||||
|
{
|
||||||
|
FetchCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
string QueryUrl = "helix/streams/followed?first=100&user_id=" + DataStore.GetInstance().Store.UserData.UserId;
|
||||||
|
Streams following = MakeRequest<Streams>(QueryUrl);
|
||||||
|
|
||||||
|
if (currentlyLive != null)
|
||||||
|
{
|
||||||
|
following.Data.ForEach(x =>
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
foreach (StreamsData sd in currentlyLive)
|
||||||
|
{
|
||||||
|
if (sd.UserId == x.UserId) found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
UserData streamer = FetchUserData(x.UserId);
|
||||||
|
Notification.GetInstance().sendNotification(streamer.DisplayName, "https://twitch.tv/" + streamer.UserName, streamer.ProfileImage, x.ThumbnailImg, x.Title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentlyLive = following.Data;
|
||||||
|
}catch(System.Exception ex)
|
||||||
|
{
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
Dictionary<string, string> postData = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
postData["client_id"] = TwitchClientID;
|
||||||
|
postData["client_secret"] = TwitchClientSecret;
|
||||||
|
postData["grant_type"] = "refresh_token";
|
||||||
|
postData["refresh_token"] = DataStore.GetInstance().Store.Authentication.RefreshToken;
|
||||||
|
|
||||||
|
byte[] byteArray = buildPostData(postData);
|
||||||
|
|
||||||
|
WebRequest request = WebRequest.Create("https://id.twitch.tv/oauth2/token");
|
||||||
|
request.Method = "POST";
|
||||||
|
request.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
request.ContentLength = byteArray.Length;
|
||||||
|
Stream dataStream = request.GetRequestStream();
|
||||||
|
dataStream.Write(byteArray, 0, byteArray.Length);
|
||||||
|
dataStream.Close();
|
||||||
|
WebResponse response = request.GetResponse();
|
||||||
|
dataStream = response.GetResponseStream();
|
||||||
|
StreamReader reader = new StreamReader(dataStream);
|
||||||
|
string responseFromServer = reader.ReadToEnd();
|
||||||
|
reader.Close();
|
||||||
|
dataStream.Close();
|
||||||
|
response.Close();
|
||||||
|
|
||||||
|
DataStore.GetInstance().Store.Authentication = JsonSerializer.Deserialize<Authentication>(responseFromServer);
|
||||||
|
DateTime unixStart = DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc);
|
||||||
|
DataStore.GetInstance().Store.Authentication.ExpiresAt = (long)Math.Floor((DateTime.Now.AddSeconds(DataStore.GetInstance().Store.Authentication.ExpiresSeconds) - unixStart).TotalMilliseconds);
|
||||||
|
DataStore.GetInstance().Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async public void BeginConnection()
|
||||||
|
{
|
||||||
|
guid = Guid.NewGuid().ToString();
|
||||||
|
WebServer.GetInstance().TwitchState = guid;
|
||||||
|
Process myProcess = new Process();
|
||||||
|
myProcess.StartInfo.UseShellExecute = true;
|
||||||
|
myProcess.StartInfo.FileName = String.Format("https://id.twitch.tv/oauth2/authorize?&redirect_uri=http://localhost:32584/twitchRedirect&scope=user:read:subscriptions%20user:read:follows%20user:read:email%20openid&response_type=code&state={0}&nonce={1}&client_id={2}", guid, guid, TwitchClientID);
|
||||||
|
myProcess.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string endConnection(string code)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> postData = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
postData["client_id"] = TwitchClientID;
|
||||||
|
postData["client_secret"] = TwitchClientSecret;
|
||||||
|
postData["grant_type"] = "authorization_code";
|
||||||
|
postData["redirect_uri"] = "http://localhost:32584/twitchRedirect";
|
||||||
|
postData["code"] = code;
|
||||||
|
|
||||||
|
byte[] byteArray = buildPostData(postData);
|
||||||
|
|
||||||
|
WebRequest request = WebRequest.Create("https://id.twitch.tv/oauth2/token");
|
||||||
|
request.Method = "POST";
|
||||||
|
request.ContentType = "application/x-www-form-urlencoded";
|
||||||
|
request.ContentLength = byteArray.Length;
|
||||||
|
Stream dataStream = request.GetRequestStream();
|
||||||
|
dataStream.Write(byteArray, 0, byteArray.Length);
|
||||||
|
dataStream.Close();
|
||||||
|
WebResponse response = request.GetResponse();
|
||||||
|
dataStream = response.GetResponseStream();
|
||||||
|
StreamReader reader = new StreamReader(dataStream);
|
||||||
|
string responseFromServer = reader.ReadToEnd();
|
||||||
|
reader.Close();
|
||||||
|
dataStream.Close();
|
||||||
|
response.Close();
|
||||||
|
return responseFromServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
TwitchDesktopNotifications/Core/WebServer.cs
Normal file
112
TwitchDesktopNotifications/Core/WebServer.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Web;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.Core
|
||||||
|
{
|
||||||
|
internal class WebServer
|
||||||
|
{
|
||||||
|
public int Port = 32584;
|
||||||
|
|
||||||
|
private HttpListener listener;
|
||||||
|
|
||||||
|
private static WebServer Instance;
|
||||||
|
|
||||||
|
public static WebServer GetInstance()
|
||||||
|
{
|
||||||
|
if(Instance == null)
|
||||||
|
{
|
||||||
|
Instance= new WebServer();
|
||||||
|
}
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String TwitchCode { get; private set; }
|
||||||
|
public String TwitchState { get; set; }
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
listener = new HttpListener();
|
||||||
|
listener.Prefixes.Add("http://127.0.0.1:" + Port.ToString() + "/");
|
||||||
|
listener.Prefixes.Add("http://localhost:" + Port.ToString() + "/");
|
||||||
|
listener.Start();
|
||||||
|
new Thread(new ThreadStart(ThreadManagedServer)).Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler CodeRecived;
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
listener.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RespondConnection(HttpListenerRequest request, HttpListenerResponse response)
|
||||||
|
{
|
||||||
|
var query = HttpUtility.ParseQueryString(request.Url.Query);
|
||||||
|
if (request.HttpMethod == "GET" && query["state"] == this.TwitchState)
|
||||||
|
{
|
||||||
|
this.TwitchCode = query["code"];
|
||||||
|
response.StatusCode = (int)HttpStatusCode.OK;
|
||||||
|
response.ContentType = "text/html";
|
||||||
|
response.OutputStream.Write(Encoding.ASCII.GetBytes("<!DOCTYPE html><html><head><title>Twitch Connected!</title><style>p.title{font-size:20px;font-weight:bold;margin-top:0px;}.container{width:240px;border:2px solid #bf94ff;padding:20px;margin:auto;border-radius:10px;}button{margin-left:195px;}</style></head><body><div class=\"container\"><p class=\"title\">Twitch Desktop Notification</p><p class=\"msg\">Twitch has been success fully connected. Please close this tab.</p><button onclick=\"javascript:window.close();\">Close</button></div></body></html>"));
|
||||||
|
response.OutputStream.Close();
|
||||||
|
CodeRecived?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{;
|
||||||
|
response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
response.ContentType = "text/html";
|
||||||
|
response.OutputStream.Write(Encoding.ASCII.GetBytes("<!DOCTYPE html><html><head><title>State Missmatch</title></head><body><h1>State Missmatch</h1><p>State does not match up preventing XSS.</p></body></html>"));
|
||||||
|
response.OutputStream.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RespondFavicon(HttpListenerResponse response)
|
||||||
|
{
|
||||||
|
response.StatusCode = (int)HttpStatusCode.OK;
|
||||||
|
response.ContentType = "image/x-icon";
|
||||||
|
response.OutputStream.Write(File.ReadAllBytes("Assets/icon.ico"));
|
||||||
|
response.OutputStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRequestThread(object? obj)
|
||||||
|
{
|
||||||
|
HttpListenerContext context = (HttpListenerContext)obj;
|
||||||
|
HttpListenerRequest request = context.Request;
|
||||||
|
|
||||||
|
if (request.Url.AbsolutePath == "/favicon.ico")
|
||||||
|
{
|
||||||
|
RespondFavicon(context.Response);
|
||||||
|
}
|
||||||
|
else if (request.Url.AbsolutePath == "/twitchRedirect")
|
||||||
|
{
|
||||||
|
RespondConnection(request, context.Response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpListenerResponse response = context.Response;
|
||||||
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
response.ContentType = "text/html";
|
||||||
|
response.OutputStream.Write(Encoding.ASCII.GetBytes("<!DOCTYPE html><html><head><title>Not Found</title></head><body><h1>Not Found</h1><p>File not found</p></body></html>"));
|
||||||
|
response.OutputStream.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThreadManagedServer()
|
||||||
|
{
|
||||||
|
while (listener.IsListening)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpListenerContext context = listener.GetContext();
|
||||||
|
ParameterizedThreadStart pts = new ParameterizedThreadStart(processRequestThread);
|
||||||
|
pts.Invoke(context);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
TwitchDesktopNotifications/DataStore.cs
Normal file
65
TwitchDesktopNotifications/DataStore.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using TwitchDesktopNotifications.JsonStructure;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications
|
||||||
|
{
|
||||||
|
internal class DataStore
|
||||||
|
{
|
||||||
|
private DataStore() { }
|
||||||
|
|
||||||
|
public static DataStore Instance { get; private set; }
|
||||||
|
private Store _store;
|
||||||
|
public JsonStructure.Store Store {
|
||||||
|
get {
|
||||||
|
if (_store == null)
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
return _store;
|
||||||
|
}
|
||||||
|
private set {
|
||||||
|
_store = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool isLoaded { get; private set; }
|
||||||
|
|
||||||
|
public static DataStore GetInstance()
|
||||||
|
{
|
||||||
|
if(Instance == null)
|
||||||
|
{
|
||||||
|
Instance = new DataStore();
|
||||||
|
}
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
String FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TwitchNotify");
|
||||||
|
String FileName = "store.json";
|
||||||
|
|
||||||
|
string fileContent = JsonSerializer.Serialize<JsonStructure.Store>(Store);
|
||||||
|
Directory.CreateDirectory(FilePath);
|
||||||
|
File.WriteAllText(FilePath + "/" + FileName, fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load() {
|
||||||
|
String FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TwitchNotify");
|
||||||
|
String FileName = "store.json";
|
||||||
|
|
||||||
|
Directory.CreateDirectory(FilePath);
|
||||||
|
|
||||||
|
if(File.Exists(FilePath+"/"+ FileName)) {
|
||||||
|
|
||||||
|
string fileContent = File.ReadAllText(FilePath+"/"+ FileName);
|
||||||
|
Store = JsonSerializer.Deserialize<JsonStructure.Store>(fileContent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Store = new JsonStructure.Store();
|
||||||
|
}
|
||||||
|
isLoaded= true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
TwitchDesktopNotifications/JsonStructure/Authentication.cs
Normal file
43
TwitchDesktopNotifications/JsonStructure/Authentication.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure
|
||||||
|
{
|
||||||
|
internal class Authentication
|
||||||
|
{
|
||||||
|
[JsonPropertyName("access_token")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("expires_in")]
|
||||||
|
public int ExpiresSeconds { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("refresh_token")]
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("scope")]
|
||||||
|
public List<string> Scopes { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("token_type")]
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("expiresAt")]
|
||||||
|
public long ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public DateTime ExpiresAsDate {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (new DateTime(1970, 1, 1)).AddMilliseconds(ExpiresAt);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
DateTime unixStart = DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc);
|
||||||
|
ExpiresAt = (long)Math.Floor((value.ToUniversalTime() - unixStart).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
TwitchDesktopNotifications/JsonStructure/Helix/Pagination.cs
Normal file
17
TwitchDesktopNotifications/JsonStructure/Helix/Pagination.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class Pagination
|
||||||
|
{
|
||||||
|
public Pagination() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("cursor")]
|
||||||
|
public string Cursor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
TwitchDesktopNotifications/JsonStructure/Helix/Streams.cs
Normal file
17
TwitchDesktopNotifications/JsonStructure/Helix/Streams.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class Streams
|
||||||
|
{
|
||||||
|
public Streams() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public List<StreamsData> Data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class StreamsData
|
||||||
|
{
|
||||||
|
public StreamsData() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_name")]
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("thumbnail_url")]
|
||||||
|
public string ThumbnailImg { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
TwitchDesktopNotifications/JsonStructure/Helix/User.cs
Normal file
17
TwitchDesktopNotifications/JsonStructure/Helix/User.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class User
|
||||||
|
{
|
||||||
|
public User() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public List<UserData> Data { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
47
TwitchDesktopNotifications/JsonStructure/Helix/UserData.cs
Normal file
47
TwitchDesktopNotifications/JsonStructure/Helix/UserData.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class UserData
|
||||||
|
{
|
||||||
|
public UserData() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("login")]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("display_name")]
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("broadcaster_type")]
|
||||||
|
public string BroadcasterType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("profile_image_url")]
|
||||||
|
public string ProfileImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("offline_image_url")]
|
||||||
|
public string OfflineImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("view_count")]
|
||||||
|
public int TotalViewers { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string EMail { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("created_at")]
|
||||||
|
public string CreatedISODate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class UserFollows
|
||||||
|
{
|
||||||
|
public UserFollows() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public List<UsersFollowsData> results { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("pagination")]
|
||||||
|
public Pagination Pagination { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure.Helix
|
||||||
|
{
|
||||||
|
internal class UsersFollowsData
|
||||||
|
{
|
||||||
|
public UsersFollowsData() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("from_id")]
|
||||||
|
public string FromID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("from_login")]
|
||||||
|
public string FromUserName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("from_name")]
|
||||||
|
public string FromDisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("to_id")]
|
||||||
|
public string ToID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("to_login")]
|
||||||
|
public string ToUserName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("to_name")]
|
||||||
|
public string ToDisplayName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("followed_at")]
|
||||||
|
public string FollowedISODateTime { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
21
TwitchDesktopNotifications/JsonStructure/Store.cs
Normal file
21
TwitchDesktopNotifications/JsonStructure/Store.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TwitchDesktopNotifications.JsonStructure.Helix;
|
||||||
|
|
||||||
|
namespace TwitchDesktopNotifications.JsonStructure
|
||||||
|
{
|
||||||
|
internal class Store
|
||||||
|
{
|
||||||
|
public Store() { }
|
||||||
|
|
||||||
|
[JsonPropertyName("authentication")]
|
||||||
|
public Authentication Authentication { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_data")]
|
||||||
|
public UserData UserData { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
109
TwitchDesktopNotifications/Program.cs
Normal file
109
TwitchDesktopNotifications/Program.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using TwitchDesktopNotifications;
|
||||||
|
using TwitchDesktopNotifications.Core;
|
||||||
|
using TwitchDesktopNotifications.JsonStructure;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
|
||||||
|
static bool isConnecting = false;
|
||||||
|
static WebServer ws = WebServer.GetInstance();
|
||||||
|
|
||||||
|
private static NotifyIcon notifyIcon;
|
||||||
|
private static ContextMenuStrip cms;
|
||||||
|
|
||||||
|
|
||||||
|
public static void Ws_CodeRecived(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ws.CodeRecived -= Ws_CodeRecived;
|
||||||
|
|
||||||
|
string response = TwitchFetcher.GetInstance().endConnection(((WebServer)sender).TwitchCode);
|
||||||
|
|
||||||
|
if (!DataStore.GetInstance().isLoaded)
|
||||||
|
{
|
||||||
|
DataStore.GetInstance().Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStore.GetInstance().Store.Authentication = JsonSerializer.Deserialize<Authentication>(response);
|
||||||
|
|
||||||
|
DateTime unixStart = DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc);
|
||||||
|
DataStore.GetInstance().Store.Authentication.ExpiresAt = (long)Math.Floor((DateTime.Now.AddSeconds(DataStore.GetInstance().Store.Authentication.ExpiresSeconds) - unixStart).TotalMilliseconds);
|
||||||
|
DataStore.GetInstance().Save();
|
||||||
|
|
||||||
|
isConnecting = false;
|
||||||
|
ws.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void Reconnect_Click(object? sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
TriggerAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void Quit_Click(object? sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
notifyIcon.Visible = false;
|
||||||
|
notifyIcon.Dispose();
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async static void TriggerAuthentication()
|
||||||
|
{
|
||||||
|
ws.CodeRecived += Ws_CodeRecived;
|
||||||
|
ws.Start();
|
||||||
|
isConnecting = true;
|
||||||
|
TwitchFetcher.GetInstance().BeginConnection();
|
||||||
|
if (DataStore.GetInstance().Store.Authentication == null)
|
||||||
|
{
|
||||||
|
var timerForCrash = new PeriodicTimer(TimeSpan.FromSeconds(10));
|
||||||
|
await timerForCrash.WaitForNextTickAsync();
|
||||||
|
if (isConnecting)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Twitch Connection not authenticated Exiting for saftey.", "Twitch Notify");
|
||||||
|
notifyIcon.Visible = false;
|
||||||
|
notifyIcon.Dispose();
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
|
||||||
|
|
||||||
|
notifyIcon = new NotifyIcon();
|
||||||
|
notifyIcon.Icon = new Icon("Assets/icon.ico");
|
||||||
|
notifyIcon.Text = "Notify";
|
||||||
|
|
||||||
|
cms = new ContextMenuStrip();
|
||||||
|
|
||||||
|
cms.Items.Add(new ToolStripMenuItem("Reconnect", null, new EventHandler(Reconnect_Click)));
|
||||||
|
cms.Items.Add(new ToolStripSeparator());
|
||||||
|
cms.Items.Add(new ToolStripMenuItem("Quit", null, new EventHandler(Quit_Click), "Quit"));
|
||||||
|
|
||||||
|
notifyIcon.ContextMenuStrip = cms;
|
||||||
|
notifyIcon.Visible = true;
|
||||||
|
|
||||||
|
if (DataStore.GetInstance().Store.Authentication == null)
|
||||||
|
{
|
||||||
|
TriggerAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.Sleep(10000);
|
||||||
|
TwitchFetcher.GetInstance().GetLiveFollowingUsers();
|
||||||
|
}
|
||||||
|
}).Start();
|
||||||
|
|
||||||
|
Application.Run();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
33
TwitchDesktopNotifications/TwitchDesktopNotifications.csproj
Normal file
33
TwitchDesktopNotifications/TwitchDesktopNotifications.csproj
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||||
|
<PackageId>Twitch Notify</PackageId>
|
||||||
|
<Authors>KeareanGaming</Authors>
|
||||||
|
<AssemblyName>Twitch Notify</AssemblyName>
|
||||||
|
<UseWPF>True</UseWPF>
|
||||||
|
<UseWindowsForms>True</UseWindowsForms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Assets\icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="8.0.0-build.65" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Assets\icon.ico">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Assets\twitch.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user