19 Commits

Author SHA1 Message Date
Martin Barker
f5367c826d Added Support for setup system, added support for using Signing Certificate. 2023-02-23 21:57:52 +00:00
Martin Barker
d42bf9a332 increased notification time to 10 mins 2023-02-23 17:16:34 +00:00
Martin Barker
d8ca0f01ca Updated form design a little to make it cleaner and fix a bug for ignore not changing 2023-02-21 22:53:16 +00:00
Martin Barker (Keatran)
edddd01a21 Merge pull request #3 from barkermn01/SelectedStreamers
Updated Ignore form to be a little cleaner, and hyperlinks now work.
2023-02-21 22:35:20 +00:00
Martin Barker
45d4dad22a Updated Ignore form to be a little cleaner, and hyperlinks now work. 2023-02-21 22:34:56 +00:00
Martin Barker (Keatran)
a5ef763617 Merge pull request #2 from barkermn01/SelectedStreamers
Selected streamers
2023-02-21 21:27:27 +00:00
Martin Barker
49ea2ba2a6 Added Support for Debug Logging
Updated a couple of variables for better memory use.
Added Support for ignoring specific streamers
Moved to using a SingletonFactory for Singlton Classes
No Longer Using MessageBox for disconnected message
Added better detection for disconnected to stop it poping with other problems
Added Window and menu option to open window to manage ignored streamers
2023-02-21 21:25:38 +00:00
Martin Barker
03b620ecec Started work on #1 2023-02-03 18:59:07 +00:00
Martin Barker (Keatran)
ab758cd85f Update Readme.md 2023-01-30 14:49:18 +00:00
Martin Barker (Keatran)
ed3a2bb5b9 Update Readme.md 2023-01-30 14:48:28 +00:00
Martin Barker
0af2d24b4c Fixed the Clean up system so that is actually cleans up the stream thumbnails 2023-01-28 15:32:59 +00:00
Martin Barker
7946c8ec37 Added some Checks to make sure we're not contantly downloading profile images if we have it cached already, and clean up thumbnails when the toast is finished. 2023-01-28 15:24:45 +00:00
Martin Barker
e9f291f6f7 Updates to finally secure the Twitch codes so i don't have to keep adding them for compile and removing them for Source Distribution. fixed a Bug in the notification system that ment dismiss did not work. 2023-01-28 15:17:50 +00:00
Martin Barker (Keatran)
77cae0c9c8 Update Readme.md 2023-01-28 03:02:26 +00:00
Martin Barker (Keatran)
6ff5c89e45 Update Readme.md 2023-01-28 03:02:11 +00:00
Martin Barker (Keatran)
afc4aa0d29 Update Readme.md 2023-01-28 03:01:47 +00:00
Martin Barker
3ccdbbdf57 Updated to fix some bugs 2023-01-28 02:47:54 +00:00
Martin Barker
b09eb0a641 Remvoed secrets for Twitch since they don't work 2023-01-28 02:13:17 +00:00
Martin Barker
36ad4fff02 Added Protection for the Twitch Secrets 2023-01-28 00:39:34 +00:00
33 changed files with 1889 additions and 205 deletions

10
.gitignore vendored
View File

@@ -6,3 +6,13 @@
/TwitchDesktopNotifications/obj
/.vs
/TwitchDesktopNotifications/App.config
/TwitchDesktopNotifications/TwitchDetails.cs
/Twitchy-SetupFiles/TwitchySetup.msi
/Twitchy-SetupFiles/TwitchySetup.exe
/Twitchy-cache/part1/Twitchy1.cab
/Twitchy-cache/part1/output-info.ini
/Twitchy-cache/part1
/Twitchy-cache/cacheIndex.txt
/Twitchy-cache
/TwitchDesktopNotifications/Twitchy.zip
/SetupFiles

789
CodeDependencies.iss Normal file
View File

@@ -0,0 +1,789 @@
; -- CodeDependencies.iss --
;
; This script shows how to download and install any dependency such as .NET,
; Visual C++ or SQL Server during your application's installation process.
;
; contribute: https://github.com/DomGries/InnoDependencyInstaller
; -----------
; SHARED CODE
; -----------
[Code]
// types and variables
type
TDependency_Entry = record
Filename: String;
Parameters: String;
Title: String;
URL: String;
Checksum: String;
ForceSuccess: Boolean;
RestartAfter: Boolean;
end;
var
Dependency_Memo: String;
Dependency_List: array of TDependency_Entry;
Dependency_NeedRestart, Dependency_ForceX86: Boolean;
Dependency_DownloadPage: TDownloadWizardPage;
procedure Dependency_Add(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, RestartAfter: Boolean);
var
Dependency: TDependency_Entry;
DependencyCount: Integer;
begin
Dependency_Memo := Dependency_Memo + #13#10 + '%1' + Title;
Dependency.Filename := Filename;
Dependency.Parameters := Parameters;
Dependency.Title := Title;
if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
Dependency.URL := '';
end else begin
Dependency.URL := URL;
end;
Dependency.Checksum := Checksum;
Dependency.ForceSuccess := ForceSuccess;
Dependency.RestartAfter := RestartAfter;
DependencyCount := GetArrayLength(Dependency_List);
SetArrayLength(Dependency_List, DependencyCount + 1);
Dependency_List[DependencyCount] := Dependency;
end;
<event('InitializeWizard')>
procedure Dependency_Internal1;
begin
Dependency_DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
end;
<event('PrepareToInstall')>
function Dependency_Internal2(var NeedsRestart: Boolean): String;
var
DependencyCount, DependencyIndex, ResultCode: Integer;
Retry: Boolean;
TempValue: String;
begin
DependencyCount := GetArrayLength(Dependency_List);
if DependencyCount > 0 then begin
Dependency_DownloadPage.Show;
for DependencyIndex := 0 to DependencyCount - 1 do begin
if Dependency_List[DependencyIndex].URL <> '' then begin
Dependency_DownloadPage.Clear;
Dependency_DownloadPage.Add(Dependency_List[DependencyIndex].URL, Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Checksum);
Retry := True;
while Retry do begin
Retry := False;
try
Dependency_DownloadPage.Download;
except
if Dependency_DownloadPage.AbortedByUser then begin
Result := Dependency_List[DependencyIndex].Title;
DependencyIndex := DependencyCount;
end else begin
case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
IDABORT: begin
Result := Dependency_List[DependencyIndex].Title;
DependencyIndex := DependencyCount;
end;
IDRETRY: begin
Retry := True;
end;
end;
end;
end;
end;
end;
end;
if Result = '' then begin
for DependencyIndex := 0 to DependencyCount - 1 do begin
Dependency_DownloadPage.SetText(Dependency_List[DependencyIndex].Title, '');
Dependency_DownloadPage.SetProgress(DependencyIndex + 1, DependencyCount + 1);
while True do begin
ResultCode := 0;
if ShellExec('', ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
if Dependency_List[DependencyIndex].RestartAfter then begin
if DependencyIndex = DependencyCount - 1 then begin
Dependency_NeedRestart := True;
end else begin
NeedsRestart := True;
Result := Dependency_List[DependencyIndex].Title;
end;
break;
end else if (ResultCode = 0) or Dependency_List[DependencyIndex].ForceSuccess then begin // ERROR_SUCCESS (0)
break;
end else if ResultCode = 1641 then begin // ERROR_SUCCESS_REBOOT_INITIATED (1641)
NeedsRestart := True;
Result := Dependency_List[DependencyIndex].Title;
break;
end else if ResultCode = 3010 then begin // ERROR_SUCCESS_REBOOT_REQUIRED (3010)
Dependency_NeedRestart := True;
break;
end;
end;
case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependency_List[DependencyIndex].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
IDABORT: begin
Result := Dependency_List[DependencyIndex].Title;
break;
end;
IDIGNORE: begin
break;
end;
end;
end;
if Result <> '' then begin
break;
end;
end;
if NeedsRestart then begin
TempValue := '"' + ExpandConstant('{srcexe}') + '" /restart=1 /LANG="' + ExpandConstant('{language}') + '" /DIR="' + WizardDirValue + '" /GROUP="' + WizardGroupValue + '" /TYPE="' + WizardSetupType(False) + '" /COMPONENTS="' + WizardSelectedComponents(False) + '" /TASKS="' + WizardSelectedTasks(False) + '"';
if WizardNoIcons then begin
TempValue := TempValue + ' /NOICONS';
end;
RegWriteStringValue(HKA, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', '{#SetupSetting("AppName")}', TempValue);
end;
end;
Dependency_DownloadPage.Hide;
end;
end;
<event('UpdateReadyMemo')>
function Dependency_Internal3(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
begin
Result := '';
if MemoUserInfoInfo <> '' then begin
Result := Result + MemoUserInfoInfo + Newline + NewLine;
end;
if MemoDirInfo <> '' then begin
Result := Result + MemoDirInfo + Newline + NewLine;
end;
if MemoTypeInfo <> '' then begin
Result := Result + MemoTypeInfo + Newline + NewLine;
end;
if MemoComponentsInfo <> '' then begin
Result := Result + MemoComponentsInfo + Newline + NewLine;
end;
if MemoGroupInfo <> '' then begin
Result := Result + MemoGroupInfo + Newline + NewLine;
end;
if MemoTasksInfo <> '' then begin
Result := Result + MemoTasksInfo;
end;
if Dependency_Memo <> '' then begin
if MemoTasksInfo = '' then begin
Result := Result + SetupMessage(msgReadyMemoTasks);
end;
Result := Result + FmtMessage(Dependency_Memo, [Space]);
end;
end;
<event('NeedRestart')>
function Dependency_Internal4: Boolean;
begin
Result := Dependency_NeedRestart;
end;
function Dependency_IsX64: Boolean;
begin
Result := not Dependency_ForceX86 and Is64BitInstallMode;
end;
function Dependency_String(const x86, x64: String): String;
begin
if Dependency_IsX64 then begin
Result := x64;
end else begin
Result := x86;
end;
end;
function Dependency_ArchSuffix: String;
begin
Result := Dependency_String('', '_x64');
end;
function Dependency_ArchTitle: String;
begin
Result := Dependency_String(' (x86)', ' (x64)');
end;
function Dependency_IsNetCoreInstalled(const Version: String): Boolean;
var
ResultCode: Integer;
begin
// source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe') then begin
ExtractTemporaryFile('netcorecheck' + Dependency_ArchSuffix + '.exe');
end;
StringChangeEx(Version, ' ', ' -v ', True)
Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe', ' -n ' + Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;
procedure Dependency_AddDotNet35;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net35-sp1
if not IsDotNetInstalled(net35, 1) then begin
Dependency_Add('dotnetfx35.exe',
'/lang:enu /passive /norestart',
'.NET Framework 3.5 Service Pack 1',
'https://download.microsoft.com/download/2/0/E/20E90413-712F-438C-988E-FDAA79A8AC3D/dotnetfx35.exe',
'', False, False);
end;
end;
procedure Dependency_AddDotNet40;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net40
if not IsDotNetInstalled(net4full, 0) then begin
Dependency_Add('dotNetFx40_Full_setup.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Framework 4.0',
'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe',
'', False, False);
end;
end;
procedure Dependency_AddDotNet45;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net452
if not IsDotNetInstalled(net452, 0) then begin
Dependency_Add('dotnetfx45.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Framework 4.5.2',
'https://go.microsoft.com/fwlink/?LinkId=397707',
'', False, False);
end;
end;
procedure Dependency_AddDotNet46;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net462
if not IsDotNetInstalled(net462, 0) then begin
Dependency_Add('dotnetfx46.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Framework 4.6.2',
'https://go.microsoft.com/fwlink/?linkid=780596',
'', False, False);
end;
end;
procedure Dependency_AddDotNet47;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net472
if not IsDotNetInstalled(net472, 0) then begin
Dependency_Add('dotnetfx47.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Framework 4.7.2',
'https://go.microsoft.com/fwlink/?LinkId=863262',
'', False, False);
end;
end;
procedure Dependency_AddDotNet48;
begin
// https://dotnet.microsoft.com/download/dotnet-framework/net48
if not IsDotNetInstalled(net48, 0) then begin
Dependency_Add('dotnetfx48.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Framework 4.8',
'https://go.microsoft.com/fwlink/?LinkId=2085155',
'', False, False);
end;
end;
procedure Dependency_AddNetCore31;
begin
// https://dotnet.microsoft.com/download/dotnet-core/3.1
if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 3.1.22') then begin
Dependency_Add('netcore31' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Core Runtime 3.1.22' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c2437aed-8cc4-41d0-a239-d6c7cf7bddae/062c37e8b06df740301c0bca1b0b7b9a/dotnet-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/4e95705e-1bb6-4764-b899-1b97eb70ea1d/dd311e073bd3e25b2efe2dcf02727e81/dotnet-runtime-3.1.22-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddNetCore31Asp;
begin
// https://dotnet.microsoft.com/download/dotnet-core/3.1
if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 3.1.22') then begin
Dependency_Add('netcore31asp' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'ASP.NET Core Runtime 3.1.22' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/0a1a2ee5-b8ed-4f0d-a4af-a7bce9a9ac2b/d452039b49d79e8897f272c3ab34b875/aspnetcore-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/80e52143-31e8-450e-aa94-b3f8484aaba9/4b69e5c77d50e7b367960a0079c90a99/aspnetcore-runtime-3.1.22-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddNetCore31Desktop;
begin
// https://dotnet.microsoft.com/download/dotnet-core/3.1
if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 3.1.22') then begin
Dependency_Add('netcore31desktop' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Desktop Runtime 3.1.22' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/e4fcd574-4487-4b4b-8ca8-c23177c6f59f/c6d67a04956169dc21895cdcb42bf344/windowsdesktop-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1c14e24b-7f31-42dc-ba3c-83295a2d6f7e/41b93591162dfe556cc160ae44fbe75e/windowsdesktop-runtime-3.1.22-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet50;
begin
// https://dotnet.microsoft.com/download/dotnet/5.0
if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 5.0.13') then begin
Dependency_Add('dotnet50' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Runtime 5.0.13' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4a79fcd5-d61b-4606-8496-68071c8099c6/2bf770ca40521e8c4563072592eadd06/dotnet-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/fccf43d2-3e62-4ede-b5a5-592a7ccded7b/6339f1fdfe3317df5b09adf65f0261ab/dotnet-runtime-5.0.13-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet50Asp;
begin
// https://dotnet.microsoft.com/download/dotnet/5.0
if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 5.0.13') then begin
Dependency_Add('dotnet50asp' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'ASP.NET Core Runtime 5.0.13' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/340f9482-fc43-4ef7-b434-e2ed57f55cb3/c641b805cef3823769409a6dbac5746b/aspnetcore-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/aac560f3-eac8-437e-aebd-9830119deb10/6a3880161cf527e4ec71f67efe4d91ad/aspnetcore-runtime-5.0.13-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet50Desktop;
begin
// https://dotnet.microsoft.com/download/dotnet/5.0
if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.13') then begin
Dependency_Add('dotnet50desktop' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Desktop Runtime 5.0.13' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c8125c6b-d399-4be3-b201-8f1394fc3b25/724758f754fc7b67daba74db8d6d91d9/windowsdesktop-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/2bfb80f2-b8f2-44b0-90c1-d3c8c1c8eac8/409dd3d3367feeeda048f4ff34b32e82/windowsdesktop-runtime-5.0.13-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet60;
begin
// https://dotnet.microsoft.com/download/dotnet/6.0
if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 6.0.11') then begin
Dependency_Add('dotnet60' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Runtime 6.0.11' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/719bfd7c-bce2-4e73-937c-cbd7a7ace3cb/d4f570d461711d22e277f1e3487ea9c2/dotnet-runtime-6.0.11-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/8cf88855-ed09-4002-95db-8bb0f0eff051/f9006645511830bd3b840be132423768/dotnet-runtime-6.0.11-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet60Asp;
begin
// https://dotnet.microsoft.com/download/dotnet/6.0
if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 6.0.11') then begin
Dependency_Add('dotnet60asp' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'ASP.NET Core Runtime 6.0.11' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/94504599-143a-4d53-b518-74aee0ebecca/dac4a7b1f7bdc7b4e8441d6befa4941a/aspnetcore-runtime-6.0.11-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/e874914f-d43d-4b61-8479-f6a5536e44b1/7043adfe896aa9f980ce23e884aae37d/aspnetcore-runtime-6.0.11-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet60Desktop;
begin
// https://dotnet.microsoft.com/download/dotnet/6.0
if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 6.0.11') then begin
Dependency_Add('dotnet60desktop' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Desktop Runtime 6.0.11' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/2a392287-fd51-4ee8-9c15-a672ab9bc55d/03d4784b3a543a0fb9ce5677ed13a9a3/windowsdesktop-runtime-6.0.11-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/0192a249-3ec8-4374-a827-e186dd58d55d/cec046575f3eb2247a10ba3d50f5cf6c/windowsdesktop-runtime-6.0.11-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet70;
begin
// https://dotnet.microsoft.com/download/dotnet/7.0
if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 7.0.3') then begin
Dependency_Add('dotnet70' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Runtime 7.0.3' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/9dd2da29-ca47-40fb-81a0-96fe26ea8ea2/e8f7e09a6d4848b8c4a13282d964b9e1/dotnet-runtime-7.0.3-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/c69813b7-2ece-4c2e-8c45-e33006985e18/61cc8fe4693a662b2da55ad932a46446/dotnet-runtime-7.0.3-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet70Asp;
begin
// https://dotnet.microsoft.com/download/dotnet/7.0
if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 7.0.3') then begin
Dependency_Add('dotnet70asp' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'ASP.NET Core Runtime 7.0.3' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4bf0f350-f947-408b-9ee4-539313b85634/b17087052d6192b5d59735ae6f208c19/aspnetcore-runtime-7.0.3-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/d37efccc-2ba1-4fc9-a1ef-a8e1e77fb681/b9a20fc29ff05f18d81620ec88ade699/aspnetcore-runtime-7.0.3-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDotNet70Desktop;
begin
// https://dotnet.microsoft.com/download/dotnet/7.0
if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 7.0.3') then begin
Dependency_Add('dotnet70desktop' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
'.NET Desktop Runtime 7.0.3' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/fb8bf100-9e1c-472c-8bc8-aa16fff44f53/8d36f5a56edff8620f9c63c1e73ba88c/windowsdesktop-runtime-7.0.3-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/3ebf014d-fcb9-4200-b3fe-76ba2000b027/840f2f95833ce400a9949e35f1581d28/windowsdesktop-runtime-7.0.3-win-x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddVC2005;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=26347
if not IsMsiProductInstalled(Dependency_String('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), PackVersionComponents(8, 0, 61000, 0)) then begin
Dependency_Add('vcredist2005' + Dependency_ArchSuffix + '.exe',
'/q',
'Visual C++ 2005 Service Pack 1 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'),
'', False, False);
end;
end;
procedure Dependency_AddVC2008;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=26368
if not IsMsiProductInstalled(Dependency_String('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), PackVersionComponents(9, 0, 30729, 6161)) then begin
Dependency_Add('vcredist2008' + Dependency_ArchSuffix + '.exe',
'/q',
'Visual C++ 2008 Service Pack 1 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddVC2010;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=26999
if not IsMsiProductInstalled(Dependency_String('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), PackVersionComponents(10, 0, 40219, 0)) then begin
Dependency_Add('vcredist2010' + Dependency_ArchSuffix + '.exe',
'/passive /norestart',
'Visual C++ 2010 Service Pack 1 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddVC2012;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=30679
if not IsMsiProductInstalled(Dependency_String('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), PackVersionComponents(11, 0, 61030, 0)) then begin
Dependency_Add('vcredist2012' + Dependency_ArchSuffix + '.exe',
'/passive /norestart',
'Visual C++ 2012 Update 4 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddVC2013;
begin
// https://support.microsoft.com/en-us/help/4032938
if not IsMsiProductInstalled(Dependency_String('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), PackVersionComponents(12, 0, 40664, 0)) then begin
Dependency_Add('vcredist2013' + Dependency_ArchSuffix + '.exe',
'/passive /norestart',
'Visual C++ 2013 Update 5 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddVC2015To2022;
begin
// https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
if not IsMsiProductInstalled(Dependency_String('{65E5BD06-6392-3027-8C26-853107D3CF1A}', '{36F68A90-239C-34DF-B58C-64B30153CE35}'), PackVersionComponents(14, 30, 30704, 0)) then begin
Dependency_Add('vcredist2022' + Dependency_ArchSuffix + '.exe',
'/passive /norestart',
'Visual C++ 2015-2022 Redistributable' + Dependency_ArchTitle,
Dependency_String('https://aka.ms/vs/17/release/vc_redist.x86.exe', 'https://aka.ms/vs/17/release/vc_redist.x64.exe'),
'', False, False);
end;
end;
procedure Dependency_AddDirectX;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=35
Dependency_Add('dxwebsetup.exe',
'/q',
'DirectX Runtime',
'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe',
'', True, False);
end;
procedure Dependency_AddSql2008Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=30438
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(10, 50, 4000, 0)) < 0) then begin
Dependency_Add('sql2008express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2008 R2 Service Pack 2 Express',
Dependency_String('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'),
'', False, False);
end;
end;
procedure Dependency_AddSql2012Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=56042
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(11, 0, 7001, 0)) < 0) then begin
Dependency_Add('sql2012express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2012 Service Pack 4 Express',
Dependency_String('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'),
'', False, False);
end;
end;
procedure Dependency_AddSql2014Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=57473
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(12, 0, 6024, 0)) < 0) then begin
Dependency_Add('sql2014express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2014 Service Pack 3 Express',
Dependency_String('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'),
'', False, False);
end;
end;
procedure Dependency_AddSql2016Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=56840
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(13, 0, 5026, 0)) < 0) then begin
Dependency_Add('sql2016express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2016 Service Pack 2 Express',
'https://download.microsoft.com/download/3/7/6/3767D272-76A1-4F31-8849-260BD37924E4/SQLServer2016-SSEI-Expr.exe',
'', False, False);
end;
end;
procedure Dependency_AddSql2017Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=55994
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 0, 0, 0)) < 0) then begin
Dependency_Add('sql2017express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2017 Express',
'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe',
'', False, False);
end;
end;
procedure Dependency_AddSql2019Express;
var
Version: String;
PackedVersion: Int64;
begin
// https://www.microsoft.com/en-us/download/details.aspx?id=101064
if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(15, 0, 0, 0)) < 0) then begin
Dependency_Add('sql2019express' + Dependency_ArchSuffix + '.exe',
'/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
'SQL Server 2019 Express',
'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe',
'', False, False);
end;
end;
procedure Dependency_AddWebView2;
begin
if not RegValueExists(HKLM, Dependency_String('SOFTWARE', 'SOFTWARE\WOW6432Node') + '\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv') then begin
Dependency_Add('MicrosoftEdgeWebview2Setup.exe',
'/silent /install',
'WebView2 Runtime',
'https://go.microsoft.com/fwlink/p/?LinkId=2124703',
'', False, False);
end;
end;
[Setup]
; -------------
; EXAMPLE SETUP
; -------------
#ifndef Dependency_NoExampleSetup
; comment out dependency defines to disable installing them
#define UseDotNet35
#define UseDotNet40
#define UseDotNet45
#define UseDotNet46
#define UseDotNet47
#define UseDotNet48
; requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
#define UseNetCoreCheck
#ifdef UseNetCoreCheck
#define UseNetCore31
#define UseNetCore31Asp
#define UseNetCore31Desktop
#define UseDotNet50
#define UseDotNet50Asp
#define UseDotNet50Desktop
#define UseDotNet60
#define UseDotNet60Asp
#define UseDotNet60Desktop
#define UseDotNet70
#define UseDotNet70Asp
#define UseDotNet70Desktop
#endif
#define UseVC2005
#define UseVC2008
#define UseVC2010
#define UseVC2012
#define UseVC2013
#define UseVC2015To2022
; requires dxwebsetup.exe (see download link below)
;#define UseDirectX
#define UseSql2008Express
#define UseSql2012Express
#define UseSql2014Express
#define UseSql2016Express
#define UseSql2017Express
#define UseSql2019Express
#define UseWebView2
#define MyAppSetupName 'MyProgram'
#define MyAppVersion '1.0'
#define MyAppPublisher 'Inno Setup'
#define MyAppCopyright 'Copyright <20> Inno Setup'
#define MyAppURL 'https://jrsoftware.org/isinfo.php'
AppName={#MyAppSetupName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppSetupName} {#MyAppVersion}
AppCopyright={#MyAppCopyright}
VersionInfoVersion={#MyAppVersion}
VersionInfoCompany={#MyAppPublisher}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
OutputBaseFilename={#MyAppSetupName}-{#MyAppVersion}
DefaultGroupName={#MyAppSetupName}
DefaultDirName={autopf}\{#MyAppSetupName}
UninstallDisplayIcon={app}\MyProgram.exe
SourceDir=src
OutputDir={#SourcePath}\bin
AllowNoIcons=yes
PrivilegesRequired=admin
; remove next line if you only deploy 32-bit binaries and dependencies
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: en; MessagesFile: "compiler:Default.isl"
Name: nl; MessagesFile: "compiler:Languages\Dutch.isl"
Name: de; MessagesFile: "compiler:Languages\German.isl"
[Files]
#ifdef UseNetCoreCheck
; download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
; download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
Source: "netcorecheck.exe"; Flags: dontcopy noencryption
Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption
#endif
#ifdef UseDirectX
Source: "dxwebsetup.exe"; Flags: dontcopy noencryption
#endif
Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: Dependency_IsX64; Flags: ignoreversion
Source: "MyProg.exe"; DestDir: "{app}"; Check: not Dependency_IsX64; Flags: ignoreversion
[Icons]
Name: "{group}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe"
Name: "{group}\{cm:UninstallProgram,{#MyAppSetupName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe"; Tasks: desktopicon
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"
[Run]
Filename: "{app}\MyProg.exe"; Description: "{cm:LaunchProgram,{#MyAppSetupName}}"; Flags: nowait postinstall skipifsilent
[Code]
function InitializeSetup: Boolean;
begin
#ifdef UseDotNet35
Dependency_AddDotNet35;
#endif
#ifdef UseDotNet40
Dependency_AddDotNet40;
#endif
#ifdef UseDotNet45
Dependency_AddDotNet45;
#endif
#ifdef UseDotNet46
Dependency_AddDotNet46;
#endif
#ifdef UseDotNet47
Dependency_AddDotNet47;
#endif
#ifdef UseDotNet48
Dependency_AddDotNet48;
#endif
#ifdef UseNetCore31
Dependency_AddNetCore31;
#endif
#ifdef UseNetCore31Asp
Dependency_AddNetCore31Asp;
#endif
#ifdef UseNetCore31Desktop
Dependency_AddNetCore31Desktop;
#endif
#ifdef UseDotNet50
Dependency_AddDotNet50;
#endif
#ifdef UseDotNet50Asp
Dependency_AddDotNet50Asp;
#endif
#ifdef UseDotNet50Desktop
Dependency_AddDotNet50Desktop;
#endif
#ifdef UseDotNet60
Dependency_AddDotNet60;
#endif
#ifdef UseDotNet60Asp
Dependency_AddDotNet60Asp;
#endif
#ifdef UseDotNet60Desktop
Dependency_AddDotNet60Desktop;
#endif
#ifdef UseDotNet70
Dependency_AddDotNet70;
#endif
#ifdef UseDotNet70Asp
Dependency_AddDotNet70Asp;
#endif
#ifdef UseDotNet70Desktop
Dependency_AddDotNet70Desktop;
#endif
#ifdef UseVC2005
Dependency_AddVC2005;
#endif
#ifdef UseVC2008
Dependency_AddVC2008;
#endif
#ifdef UseVC2010
Dependency_AddVC2010;
#endif
#ifdef UseVC2012
Dependency_AddVC2012;
#endif
#ifdef UseVC2013
//Dependency_ForceX86 := True; // force 32-bit install of next dependencies
Dependency_AddVC2013;
//Dependency_ForceX86 := False; // disable forced 32-bit install again
#endif
#ifdef UseVC2015To2022
Dependency_AddVC2015To2022;
#endif
#ifdef UseDirectX
ExtractTemporaryFile('dxwebsetup.exe');
Dependency_AddDirectX;
#endif
#ifdef UseSql2008Express
Dependency_AddSql2008Express;
#endif
#ifdef UseSql2012Express
Dependency_AddSql2012Express;
#endif
#ifdef UseSql2014Express
Dependency_AddSql2014Express;
#endif
#ifdef UseSql2016Express
Dependency_AddSql2016Express;
#endif
#ifdef UseSql2017Express
Dependency_AddSql2017Express;
#endif
#ifdef UseSql2019Express
Dependency_AddSql2019Express;
#endif
#ifdef UseWebView2
Dependency_AddWebView2;
#endif
Result := True;
end;
#endif

View File

@@ -1,29 +1,55 @@
# Twitch Notify
## not affiliated with Twitch or Amazon
## Not affiliated with Twitch or Amazon
[![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger)
Please note that currently this is a BETA as such it does not add it's self to windows start up if you wish to add this to start up for now you can just create a shortcut to the `Twitch Notify.exe` in the directory `C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup`,
This will be fixed when this project moves out of Beta only.
## 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).
## How to use
**How to Exit:**
To exit you will have a Notification Icon / Tray icon where you can quit the application.
![Showing windows Notification Icon Area also known as the Tray Area](https://user-images.githubusercontent.com/37368/215226270-52d596d5-7811-4389-a761-3aabad7da360.png)
**What it looks like:**
![Example of the Notification](https://user-images.githubusercontent.com/37368/215226419-0ec644f1-bd2b-498f-93ed-a48acd3c824c.png)
## Development
Want to contribute? Great!
Project is built using Visual Studios 2022, must have Windows 10.0.17763 SDK installed
Project is built using Visual Studios 2022,
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>
You need to create Application to obtain a ID and Secret on [Twitch Developer Console](https://dev.twitch.tv/console)
Add a new C# Class to the project named `TwitchDetails.cs` add the following code with your ID and Secret
```cs
namespace TwitchDesktopNotifications
{
static public class TwitchDetails
{
public static string TwitchClientID = "";
public static string TwitchClientSecret = "";
}
}
```
### CommunityToolkit 8.0.0 Pre-release
Project Requests `CommunityToolkit-MainLatest` NuGET Package Source
1. Tool > NuGET Package Manager > Package Manager Settings
2. Click on Package Source (just below the select General in the Left hand column
3. Click the + icon top right
5. 4. Enter the Name `CommunityToolkit-MainLatest` and Source `https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-MainLatest/nuget/v3/index.json`
6. Click Update
7. Click Ok
## License
MIT

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwitchDesktopNotifications.Core
{
public class Logger : SingletonFactory<Logger>, Singleton
{
private string _name;
private StreamWriter sw;
public Logger()
{
_name = DateTime.Now.ToString("dd_mm_yyyy HH_mm")+".log";
}
~Logger()
{
if (sw != null)
{
sw.Flush();
sw.Close();
}
}
public StreamWriter Writer {
get {
if(sw == null)
{
String FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TwitchNotify");
sw = new StreamWriter(FilePath+_name);
}
return sw;
}
}
}
}

View File

@@ -9,12 +9,15 @@ using System.IO;
using Windows.Foundation.Collections;
using System.Diagnostics;
using System.ComponentModel;
using Windows.UI.Notifications;
using System.Net.Http;
namespace TwitchDesktopNotifications.Core
{
internal class Notification
public class Notification : SingletonFactory<Notification>, Singleton
{
private Notification() {
private WebClient webClient = new WebClient();
public Notification() {
ToastNotificationManagerCompat.OnActivated += toastArgs =>
{
// Obtain the arguments from the notification
@@ -22,25 +25,27 @@ namespace TwitchDesktopNotifications.Core
try
{
Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = true;
myProcess.StartInfo.FileName = args["streamerUrl"];
myProcess.Start();
}catch(Exception ex) { }
if (
// action is defined and set to watch
( args.Contains("action") && args["action"] == "watch" )
||
// action is not defined so the user just generally clicked on the notification
!args.Contains("action")
){
Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = true;
myProcess.StartInfo.FileName = args["streamerUrl"];
myProcess.Start();
}else if( args.Contains("action") && args["action"] == "ignore")
{
NotifyManager.AddStreamerToIgnoreList(args["streamerName"]);
}
}catch(Exception ex) {
Logger.GetInstance().Writer.WriteLine(ex.ToString());
}
};
}
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");
@@ -49,37 +54,66 @@ namespace TwitchDesktopNotifications.Core
// download there profile picture
string fileNameProfilePic = profilePic.Split("/").Last();
(new WebClient()).DownloadFile(new Uri(profilePic), FilePath+"/"+ fileNameProfilePic);
if (!File.Exists(FilePath + "/" + fileNameProfilePic))
{
webClient.DownloadFile(new Uri(profilePic), FilePath + "/" + fileNameProfilePic);
}
// download there profile picture
string fileNameThumbnailPic = streamThumbnail.Split("/").Last();
(new WebClient()).DownloadFile(new Uri(streamThumbnail),
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 =>
if (NotifyManager.ShouldNotify(streamerName))
{
toast.ExpirationTime = DateTime.Now.AddSeconds(15);
});
var builder = new ToastContentBuilder()
.AddArgument("streamerUrl", streamerUrl)
.AddArgument("streamerName", streamerName)
.AddArgument("thumbnail_path", FilePath + "/" + fileNameThumbnailPic)
.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 ")
.AddArgument("action", "watch")
.SetBackgroundActivation())
.AddButton(new ToastButton()
.SetContent("Dismiss")
.AddArgument("action", "nothing")
.SetBackgroundActivation())
.AddButton(new ToastButton()
.SetContent("Ignore")
.AddArgument("action", "ignore")
.SetBackgroundActivation());
if (title != "")
{
builder.AddText(title);
}
builder.Show(toast =>
{
toast.ExpirationTime = DateTime.Now.AddMinutes(10);
toast.Dismissed += (ToastNotification sender, ToastDismissedEventArgs args) =>
{
try
{
File.Delete(FilePath + "/" + fileNameThumbnailPic);
}
catch (Exception) { }
builder = null;
};
toast.Activated += (ToastNotification sender, object args) =>
{
try
{
File.Delete(FilePath + "/" + fileNameThumbnailPic);
}
catch (Exception) { }
builder = null;
};
});
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.Toolkit.Uwp.Notifications;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using TwitchDesktopNotifications.JsonStructure.Helix;
namespace TwitchDesktopNotifications.Core
{
internal class NotifyManager
{
public static Boolean ShouldNotify(String streamerName)
{
if(DataStore.GetInstance().Store.SteamersToIgnore == null || DataStore.GetInstance().Store.SteamersToIgnore.Streamers == null) {
return true;
}
return !UIStreamer.GetCreateStreamer(streamerName).IsIgnored;
}
public static void AddStreamerToIgnoreList(string streamerName)
{
UIStreamer.GetCreateStreamer(streamerName).IsIgnored = true;
DataStore.GetInstance().Save();
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwitchDesktopNotifications.Core
{
public interface Singleton
{
}
public class SingletonFactory<T> where T : Singleton, new()
{
public static Dictionary<String, Singleton> store = new Dictionary<String, Singleton>();
public static T GetInstance(){
string name = typeof(T).FullName;
if (!store.ContainsKey(name))
{
store.Add(name, new T());
}
return (T)store[name];
}
}
}

View File

@@ -1,33 +1,40 @@
using ABI.System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
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 TwitcherRefreshException : Exception
{
public TwitcherRefreshException(string? message, Exception? innerException) : base(message, innerException) {
}
}
internal class TwitchFetcher
{
private TwitchFetcher() { }
private TwitchFetcher() {
}
ReconnectionNeeded rnFrm;
public void OpenFailedNotification()
{
if (rnFrm == null)
{
rnFrm = new ReconnectionNeeded();
}
if (rnFrm.IsActive)
{
rnFrm.Show();
}
}
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; }
@@ -54,36 +61,59 @@ namespace TwitchDesktopNotifications.Core
private T MakeRequest<T>(string endpoint)
{
if (DataStore.GetInstance().Store == null)
{
throw new Exception("Not Authenticated");
}
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();
try
{
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"] = TwitchDetails.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);
return JsonSerializer.Deserialize<T>(responseFromServer);
}
catch (TwitcherRefreshException ex)
{
OpenFailedNotification();
}
catch(Exception ex)
{
Logger.GetInstance().Writer.WriteLineAsync(ex.ToString());
}
return default(T);
}
public void FetchCurrentUser()
{
try
{
DataStore.GetInstance().Store.UserData = MakeRequest<User>("helix/users").Data[0];
DataStore.GetInstance().Save();
}catch(System.Exception ex)
var UserList = MakeRequest<User>("helix/users");
if (UserList.Data.Count > 0) {
DataStore.GetInstance().Store.UserData = UserList.Data[0];
DataStore.GetInstance().Save();
}
}
catch (TwitcherRefreshException ex)
{
Environment.Exit(1);
OpenFailedNotification();
}
catch (Exception ex)
{
Logger.GetInstance().Writer.WriteLineAsync(ex.ToString());
}
}
@@ -91,10 +121,19 @@ namespace TwitchDesktopNotifications.Core
{
try
{
return MakeRequest<User>("helix/users?id=" + user_id).Data[0];
}catch(System.Exception ex)
var Response = MakeRequest<User>("helix/users?id=" + user_id);
if (Response.Data.Count > 0)
{
return Response.Data[0];
}
}
catch (TwitcherRefreshException ex)
{
Environment.Exit(1);
OpenFailedNotification();
}
catch (Exception ex)
{
Logger.GetInstance().Writer.WriteLineAsync(ex.ToString());
}
return null;
}
@@ -112,7 +151,7 @@ namespace TwitchDesktopNotifications.Core
string QueryUrl = "helix/streams/followed?first=100&user_id=" + DataStore.GetInstance().Store.UserData.UserId;
Streams following = MakeRequest<Streams>(QueryUrl);
if (currentlyLive != null)
if (following != null && currentlyLive != null)
{
following.Data.ForEach(x =>
{
@@ -126,48 +165,60 @@ namespace TwitchDesktopNotifications.Core
if (!found)
{
UserData streamer = FetchUserData(x.UserId);
UIStreamer.GetCreateStreamer(x.DisplayName);
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);
}
}
catch (TwitcherRefreshException ex)
{
OpenFailedNotification();
}
catch (Exception ex)
{
Logger.GetInstance().Writer.WriteLineAsync(ex.ToString());
}
}
public void Refresh()
{
Dictionary<string, string> postData = new Dictionary<string, string>();
try
{
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;
postData["client_id"] = TwitchDetails.TwitchClientID;
postData["client_secret"] = TwitchDetails.TwitchClientSecret;
postData["grant_type"] = "refresh_token";
postData["refresh_token"] = DataStore.GetInstance().Store.Authentication.RefreshToken;
byte[] byteArray = buildPostData(postData);
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();
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();
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();
}catch(Exception e)
{
throw new TwitcherRefreshException("Unable to refresh", e);
}
}
async public void BeginConnection()
@@ -176,7 +227,7 @@ namespace TwitchDesktopNotifications.Core
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.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, TwitchDetails.TwitchClientID);
myProcess.Start();
}
@@ -184,8 +235,8 @@ namespace TwitchDesktopNotifications.Core
{
Dictionary<string, string> postData = new Dictionary<string, string>();
postData["client_id"] = TwitchClientID;
postData["client_secret"] = TwitchClientSecret;
postData["client_id"] = TwitchDetails.TwitchClientID;
postData["client_secret"] = TwitchDetails.TwitchClientSecret;
postData["grant_type"] = "authorization_code";
postData["redirect_uri"] = "http://localhost:32584/twitchRedirect";
postData["code"] = code;

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Documents;
namespace TwitchDesktopNotifications.Core
{
public class UIStreamer
{
public UIStreamer() {
}
public static UIStreamer GetCreateStreamer(string name)
{
if (DataStore.GetInstance().Store.SteamersToIgnore == null)
{
DataStore.GetInstance().Store.SteamersToIgnore = new JsonStructure.SteamersToIgnore();
}
if (DataStore.GetInstance().Store.SteamersToIgnore.Streamers == null)
{
DataStore.GetInstance().Store.SteamersToIgnore.Streamers = new List<UIStreamer>();
}
UIStreamer strmr = null;
try
{
strmr = DataStore.GetInstance().Store.SteamersToIgnore.Streamers.Where((UIStreamer strmr) => strmr.Name == name).First();
}
catch { }
finally
{
if (strmr == null)
{
strmr = new UIStreamer() { IsIgnored = false, Name = name };
DataStore.GetInstance().Store.SteamersToIgnore.Streamers.Add(strmr);
DataStore.GetInstance().Save();
}
}
return strmr;
}
[JsonIgnore]
public List<UIStreamer> StreamersToIgnore
{
get
{
return DataStore.GetInstance().Store.SteamersToIgnore.Streamers;
}
}
[JsonPropertyName("IsIgnored")]
public bool IsIgnored { get; set; } = false;
[JsonPropertyName("StreamerName")]
public string Name { get; set; }
[JsonIgnore]
public string Link {
get {
return String.Format("https://www.twitch.tv/{0}", Name);
}
}
}
}

View File

@@ -5,23 +5,12 @@ using System.IO;
namespace TwitchDesktopNotifications.Core
{
internal class WebServer
public class WebServer : SingletonFactory<WebServer>, Singleton
{
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; }

View File

@@ -1,14 +1,12 @@
using System.Text.Json;
using TwitchDesktopNotifications.JsonStructure;
using System.IO;
using TwitchDesktopNotifications.Core;
namespace TwitchDesktopNotifications
{
internal class DataStore
public class DataStore : SingletonFactory<DataStore>, Singleton
{
private DataStore() { }
public static DataStore Instance { get; private set; }
private Store _store;
public JsonStructure.Store Store {
get {
@@ -25,41 +23,37 @@ namespace TwitchDesktopNotifications
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
if (!isLoaded)
{
Store = new JsonStructure.Store();
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;
}
isLoaded= true;
}
}
}

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure
{
internal class Authentication
public class Authentication
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class Pagination
public class Pagination
{
public Pagination() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class Streams
public class Streams
{
public Streams() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class StreamsData
public class StreamsData
{
public StreamsData() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class User
public class User
{
public User() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class UserData
public class UserData
{
public UserData() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class UserFollows
public class UserFollows
{
public UserFollows() { }

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace TwitchDesktopNotifications.JsonStructure.Helix
{
internal class UsersFollowsData
public class UsersFollowsData
{
public UsersFollowsData() { }

View File

@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using TwitchDesktopNotifications.Core;
namespace TwitchDesktopNotifications.JsonStructure
{
public class SteamersToIgnore
{
[JsonPropertyName("IgnoredStreamers")]
public List<UIStreamer> Streamers { get; set; } = new List<UIStreamer>();
}
}

View File

@@ -8,14 +8,28 @@ using TwitchDesktopNotifications.JsonStructure.Helix;
namespace TwitchDesktopNotifications.JsonStructure
{
internal class Store
public class Store
{
public Store() { }
[JsonPropertyName("ignore")]
public SteamersToIgnore ignore { get; set; }
[JsonPropertyName("authentication")]
public Authentication Authentication { get; set; }
[JsonPropertyName("user_data")]
public UserData UserData { get; set; }
[JsonIgnore]
public SteamersToIgnore SteamersToIgnore {
get {
if(ignore == null) { ignore = new SteamersToIgnore(); }
return ignore;
}
set {
ignore = value;
}
}
}
}

View File

@@ -0,0 +1,224 @@
{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang2057\deflangfe2057\themelang2057\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;}
{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;}
{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f43\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f44\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\f46\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f47\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f48\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f49\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\f50\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f51\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f413\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f414\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}
{\f416\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f417\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f418\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f419\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}
{\f420\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f421\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}
{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}
{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}
{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}{\*\defchp \fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap
\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1
\af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang2057\langfe2057\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp2057\langfenp2057 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang2057\langfe2057\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp2057\langfenp2057 \snext11 \ssemihidden \sunhideused
Normal Table;}}{\*\rsidtbl \rsid5513512\rsid15873127}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator Martin Barker}{\creatim\yr2023\mo2\dy23\hr20\min51}
{\revtim\yr2023\mo2\dy23\hr20\min52}{\version2}{\edmins1}{\nofpages1}{\nofwords169}{\nofchars907}{\nofcharsws1058}{\vern65}}{\*\userprops {\propname GrammarlyDocumentId}\proptype30{\staticval 7fca5dcf58e26f86e2af1a1c8daec913dd8ead382b463301162ecfe97ee9703
3}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect
\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701
\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot15873127 \nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1
\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5
\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang
{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0
\fs22\lang2057\langfe2057\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp2057\langfenp2057 {\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 MIT License
\par \hich\af37\dbch\af31505\loch\f37 Copyright (c) 2023 Martin Barker (Keatran)
\par \hich\af37\dbch\af31505\loch\f37 Permission is hereby granted, free of charge, to any person obtaining a copy}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 of this software and associated documentation files (the "Software"), to deal}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127
\hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 in the Software without restriction, including without limitation the rights}{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 copies of the Software, and\hich\af37\dbch\af31505\loch\f37 to permit persons to whom the Software is}{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 furnished to do so, subject to the following conditions:
\par \hich\af37\dbch\af31505\loch\f37 The above copyright notice and this permission notice shall be included in all}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 copies or substantial portions of the Software.
\par \hich\af37\dbch\af31505\loch\f37 THE SOFTWARE IS PROVIDED "A\hich\af37\dbch\af31505\loch\f37 S IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{
\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,}{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAG\hich\af37\dbch\af31505\loch\f37 ES OR OTHER}{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127 \hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0
\f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid15873127
\hich\af37\dbch\af31505\loch\f37 }{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512 \hich\af37\dbch\af31505\loch\f37 SOFTWARE.}{\rtlch\fcs1 \af37 \ltrch\fcs0 \f37\lang9\langfe2057\langnp9\insrsid5513512
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f
7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd
ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d
7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b
d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52
fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71
b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b
fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567
9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd
79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf
5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2
d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1
738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68
2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac
5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a
b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9
493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2
be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f
f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64
7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e
b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4
6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd
f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d
7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39
4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf
1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a
faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2
67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9
416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27
1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b
8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4
8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65
2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36
3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e
3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985
0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000
0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000
000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000
7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000
000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000
000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000}
{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;
\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text;
\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;
\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;
\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1;
\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision;
\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;
\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1;
\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;
\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;
\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;
\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;
\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;
\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;
\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;
\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;
\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;
\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore 01050000
02000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e50000000000000000000000006030
20b4c847d901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}

View File

@@ -0,0 +1,55 @@
<Window x:Class="TwitchDesktopNotifications.ManageIgnores"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TwitchDesktopNotifications"
xmlns:core="clr-namespace:TwitchDesktopNotifications.Core"
mc:Ignorable="d"
Title="Twitchy: Ignored Streamers" Height="435" Width="395" ResizeMode="CanResizeWithGrip">
<Window.DataContext>
<core:UIStreamer />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="30" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="25" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<DataGrid
Grid.Column="1"
Grid.RowSpan="1" Grid.Row="3"
CanUserResizeColumns="False"
x:Name="dgrdIgnore" ItemsSource="{Binding StreamersToIgnore}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Ignore" Binding="{Binding IsIgnored}" IsReadOnly="False" Width="50">
</DataGridCheckBoxColumn>
<DataGridTextColumn Header="Streamer Name" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridHyperlinkColumn Header="Streamer Link" Binding="{Binding Link}" Width="*">
<DataGridHyperlinkColumn.ElementStyle>
<Style TargetType="TextBlock">
<EventSetter Event="Hyperlink.Click" Handler="HyperLink_Click"/>
</Style>
</DataGridHyperlinkColumn.ElementStyle>
</DataGridHyperlinkColumn>
</DataGrid.Columns>
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="#FFF"/>
<SolidColorBrush Color="#000" x:Key="{x:Static SystemColors.HighlightTextBrushKey}" />
</DataGrid.Resources>
</DataGrid>
<Button Content="Close" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" Width="100" Grid.Row="5" Click="CloseBtn_Click" />
<TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="Changes to the ignore list are automatically saved." VerticalAlignment="Top"/>
<TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="If you can't see a streamer Twitch Notify has not seen them." VerticalAlignment="Bottom"/>
</Grid>
</Window>

View File

@@ -0,0 +1,40 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Threading;
using TwitchDesktopNotifications.Core;
namespace TwitchDesktopNotifications
{
/// <summary>
/// Interaction logic for ManageIgnores.xaml
/// </summary>
public partial class ManageIgnores : Window
{
public ManageIgnores()
{
InitializeComponent();
}
List<UIStreamer> StreamersToIgnore = DataStore.GetInstance().Store.SteamersToIgnore.Streamers;
private void CloseBtn_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void HyperLink_Click(object sender, RoutedEventArgs e)
{
string link = ((Hyperlink)e.OriginalSource).NavigateUri.OriginalString;
var psi = new ProcessStartInfo
{
FileName = link,
UseShellExecute = true
};
Process.Start(psi);
}
}
}

View File

@@ -1,5 +1,6 @@
// See https://aka.ms/new-console-template for more information
using System.Drawing;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Windows.Controls;
@@ -17,7 +18,7 @@ internal class Program
private static NotifyIcon notifyIcon;
private static ContextMenuStrip cms;
private static ManageIgnores manageIgnores;
public static void Ws_CodeRecived(object? sender, EventArgs e)
{
@@ -45,6 +46,21 @@ internal class Program
TriggerAuthentication();
}
protected static void ManageIgnores_Click(object? sender, System.EventArgs e)
{
if (manageIgnores == null) {
manageIgnores = new ManageIgnores();
manageIgnores.Closed += ManageIgnores_Closed;
}
manageIgnores.Show();
manageIgnores.Focus();
}
private static void ManageIgnores_Closed(object? sender, EventArgs e)
{
manageIgnores = null;
}
protected static void Quit_Click(object? sender, System.EventArgs e)
{
notifyIcon.Visible = false;
@@ -60,50 +76,59 @@ internal class Program
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);
TwitchFetcher.GetInstance().OpenFailedNotification();
}
}
}
private static async Task Main(string[] args)
[STAThread]
private static void 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)
try
{
TriggerAuthentication();
}
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
new Thread(() =>
{
while (true)
notifyIcon = new NotifyIcon();
notifyIcon.Icon = new Icon("Assets/icon.ico");
notifyIcon.Text = "Twitch Notify";
cms = new ContextMenuStrip();
cms.Items.Add(new ToolStripMenuItem("Manage Ignores", null, new EventHandler(ManageIgnores_Click)));
cms.Items.Add(new ToolStripSeparator());
cms.Items.Add(new ToolStripMenuItem("Reconnect", null, new EventHandler(Reconnect_Click)));
//cms.Items.Add(new ToolStripMenuItem("About", null, new EventHandler(About_Click), "Quit"));
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)
{
Thread.Sleep(10000);
TwitchFetcher.GetInstance().GetLiveFollowingUsers();
TriggerAuthentication();
}
}).Start();
Application.Run();
Thread thread = new Thread(() =>
{
while (true)
{
Thread.Sleep(10000);
if (DataStore.GetInstance().Store != null)
{
TwitchFetcher.GetInstance().GetLiveFollowingUsers();
}
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Application.Run();
}
catch (Exception e) {
Logger.GetInstance().Writer.WriteLineAsync(e.ToString());
}
}
}

View File

@@ -0,0 +1,27 @@
<Window x:Class="TwitchDesktopNotifications.ReconnectionNeeded"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TwitchDesktopNotifications"
mc:Ignorable="d"
Title="Twitchy Disconnected" Height="140" Width="335" x:Name="reconnectionNeededWin">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="40" />
<RowDefinition Height="10" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Center" Text="We have been unable to refresh your twitch connection" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3"/>
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" TextAlignment="Center" Text="Don't worry you just need to reconnect" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="3"/>
<Button Content="Thanks" HorizontalAlignment="Center" VerticalAlignment="Top" Grid.Column="2" Grid.Row="3" Padding="5" Click="Button_Click"/>
</Grid>
</Window>

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace TwitchDesktopNotifications
{
/// <summary>
/// Interaction logic for ReconnectionNeeded.xaml
/// </summary>
public partial class ReconnectionNeeded : Window
{
public ReconnectionNeeded()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
@@ -6,11 +6,22 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<PackageId>Twitch Notify</PackageId>
<Authors>KeareanGaming</Authors>
<AssemblyName>Twitch Notify</AssemblyName>
<PackageId>Twitchy</PackageId>
<Authors>Martin Barker (KeatranGaming)</Authors>
<AssemblyName>Twitchy</AssemblyName>
<UseWPF>True</UseWPF>
<UseWindowsForms>True</UseWindowsForms>
<UserSecretsId>2dfb7064-609b-41c3-a80d-a9e4d842a55d</UserSecretsId>
<SignAssembly>False</SignAssembly>
<Description>Show Windows notifications when your fav streamers go live.</Description>
<Copyright>2023 Martin Barker</Copyright>
<PackageProjectUrl>https://github.com/barkermn01/TwitchNotify/</PackageProjectUrl>
<PackageIcon>twitch.png</PackageIcon>
<RepositoryUrl>https://github.com/barkermn01/TwitchNotify/</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>twitch, notifications</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
@@ -27,7 +38,19 @@
</None>
<None Update="Assets\twitch.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="LICENSE.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="sign.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell -command &quot;if('$(ConfigurationName)' -eq 'Release'){ cd $(TargetDir); ./sign.ps1}&quot;" />
</Target>
</Project>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
<ItemGroup>
<Compile Update="ManageIgnores.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="ReconnectionNeeded.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Page Update="ManageIgnores.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="ReconnectionNeeded.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,80 @@
#clean up if existing Files
if(Test-Path -Path 'Twitchy.zip'){
Remove-Item 'Twitchy.zip';
}
# Files to be signed
$files = 'Twitchy.exe',
'Twitchy.dll';
#certificate Common Name
$cert = "Open Source Developer, MARTIN BARKER";
# get Operating system drive letter
$drive = (Get-WmiObject Win32_OperatingSystem).SystemDrive;
# setup defaults
$progFiles = $drive+"\Program Files";
$sdkArch = "x86";
# Check if this is a 64bit windows or 32 (only 64 bit will have (x86) varient of program files
$checkPath = $drive+"\Program Files (x86)\";
if(Test-Path -Path $checkPath){
"OS Architecture: Detected 64bit Windows"
$progFiles = $progFiles + " (x86)\";
$sdkArch = "x64";
} else {
"OS Architecture: Detected 32bit Windows"
$progFiles = $progFiles + "\";
}
#Check for the Windows SDK intall
$checkPath = $progFiles+"Windows Kits\10\";
"Checking for windows 10 SDK";
if(Test-Path -Path $checkPath){
$win10sdkPath = $progFiles+"Windows Kits\10\bin\";
"SDK Path: "+$win10sdkPath;
} else {
# not found end the script
"[Error] Unable to find Windows 10 SDK, is Visual Studios 2019 or newer installed?";
Break Script;
}
# get the latest SDK Version Path (ordered by name so latest will be last)
Get-ChildItem $win10sdkPath -Filter 10.* |
Foreach-Object{
# check that this SDK version has signtool
$checkPath = $_.FullName+"\"+$sdkArch+"\signtool.exe"
if(Test-Path -Path $checkPath){
#sign tool found add set this path as comptable
$SDKVersionPath = $_.FullName;
}
}
$SDKVersionPath = $SDKVersionPath+"\"+$sdkArch;
# Windows 10 signing system (signtool.exe from Windows 10 SDK)
$signPath = $SDKVersionPath+"\signtool.exe";
"Signtool Path: "+$SDKVersionPath+"\signtool.exe";
# create alias to the discovered path so we can invoke with arguments
new-alias signtool $signPath;
# basic aguments for sign tool
$appArgs = 'sign',
'/fd',
'sha256',
'/t',
'http://timestamp.comodoca.com/authenticode',
'/n',
$cert;
# add the files to be signed to the end of the args array
$appArgs = $appArgs + $files;
#run the signtool
signtool $appArgs;
# once files are signed package into zip
Get-ChildItem './' | where { $_.Name -notin 'sign.ps1'} | Compress-Archive -DestinationPath 'Twitchy.zip' -Update
iscc ../../../../setup.iss

BIN
netcorecheck.exe Normal file

Binary file not shown.

BIN
netcorecheck_x64.exe Normal file

Binary file not shown.

77
setup.iss Normal file
View File

@@ -0,0 +1,77 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Twitchy"
#define MyAppVersion "0.4.0"
#define MyAppExeName "Twitchy.exe"
#ifndef MyAppTargetFramework
#define MyAppTargetFramework "net6.0-windows"
#endif
#define public Dependency_NoExampleSetup
#include "CodeDependencies.iss"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{7774CEEE-6785-48B9-BA35-6B842F947A21}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
LicenseFile=.\TwitchDesktopNotifications\Lisence.rtf
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputDir=.\SetupFiles
OutputBaseFilename=TwitchySetup
SetupIconFile=.\TwitchDesktopNotifications\Assets\icon.ico
UninstallDisplayIcon=.\TwitchDesktopNotifications\Assets\icon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
ArchitecturesInstallIn64BitMode=x64
SignedUninstaller=yes
SignTool=signtool
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Microsoft.Toolkit.Uwp.Notifications.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Microsoft.Windows.SDK.NET.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Twitchy.deps.json"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Twitchy.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Twitchy.runtimeconfig.json"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\WinRT.Runtime.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Assets\icon.ico"; DestDir: "{app}\Assets"; Flags: ignoreversion
Source: ".\TwitchDesktopNotifications\bin\Release\net6.0-windows10.0.17763.0\Assets\twitch.png"; DestDir: "{app}\Assets"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
Source: "netcorecheck.exe"; Flags: dontcopy noencryption
Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: {app}\{cm:AppName}.exe; Description: {cm:LaunchProgram,{cm:AppName}}; Flags: nowait postinstall skipifsilent
[Registry]
Root: HKCU; Subkey: "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "Twitchy"; ValueData: """{app}\{#MyAppExeName}"""; Flags: uninsdeletevalue
[CustomMessages]
AppName=Twitchy
LaunchProgram=Start Twitchy after finishing installation
[Code]
function InitializeSetup: Boolean;
begin
Dependency_AddDotNet60Desktop;
Result := True;
end;