├── .gitignore
├── EdgeUpdateAPI
├── Classes
│ ├── EdgeFiles.cs
│ └── Result
│ │ ├── IResult.cs
│ │ ├── Result.cs
│ │ └── ResultType.cs
├── EdgeUpdate.cs
├── EdgeUpdateAPI.csproj
└── Responses
│ ├── FilesResponse.cs
│ └── LatestResponse.cs
├── LICENSE
├── MSEdgeBot.sln
├── MSEdgeBot
├── Classes
│ ├── Automation
│ │ └── SearchAutomation.cs
│ └── Helpers
│ │ └── UploadFileHelper.cs
├── DataStore
│ ├── DBEngine.cs
│ └── SharedDBcmd.cs
├── MSEdgeBot.csproj
├── Program.cs
└── TelegramSettings
│ ├── TDLibHost.cs
│ └── TelegramBotSettings.cs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Coverlet is a free, cross platform Code Coverage Tool
141 | coverage*[.json, .xml, .info]
142 |
143 | # Visual Studio code coverage results
144 | *.coverage
145 | *.coveragexml
146 |
147 | # NCrunch
148 | _NCrunch_*
149 | .*crunch*.local.xml
150 | nCrunchTemp_*
151 |
152 | # MightyMoose
153 | *.mm.*
154 | AutoTest.Net/
155 |
156 | # Web workbench (sass)
157 | .sass-cache/
158 |
159 | # Installshield output folder
160 | [Ee]xpress/
161 |
162 | # DocProject is a documentation generator add-in
163 | DocProject/buildhelp/
164 | DocProject/Help/*.HxT
165 | DocProject/Help/*.HxC
166 | DocProject/Help/*.hhc
167 | DocProject/Help/*.hhk
168 | DocProject/Help/*.hhp
169 | DocProject/Help/Html2
170 | DocProject/Help/html
171 |
172 | # Click-Once directory
173 | publish/
174 |
175 | # Publish Web Output
176 | *.[Pp]ublish.xml
177 | *.azurePubxml
178 | # Note: Comment the next line if you want to checkin your web deploy settings,
179 | # but database connection strings (with potential passwords) will be unencrypted
180 | *.pubxml
181 | *.publishproj
182 |
183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
184 | # checkin your Azure Web App publish settings, but sensitive information contained
185 | # in these scripts will be unencrypted
186 | PublishScripts/
187 |
188 | # NuGet Packages
189 | *.nupkg
190 | # NuGet Symbol Packages
191 | *.snupkg
192 | # The packages folder can be ignored because of Package Restore
193 | **/[Pp]ackages/*
194 | # except build/, which is used as an MSBuild target.
195 | !**/[Pp]ackages/build/
196 | # Uncomment if necessary however generally it will be regenerated when needed
197 | #!**/[Pp]ackages/repositories.config
198 | # NuGet v3's project.json files produces more ignorable files
199 | *.nuget.props
200 | *.nuget.targets
201 |
202 | # Microsoft Azure Build Output
203 | csx/
204 | *.build.csdef
205 |
206 | # Microsoft Azure Emulator
207 | ecf/
208 | rcf/
209 |
210 | # Windows Store app package directories and files
211 | AppPackages/
212 | BundleArtifacts/
213 | Package.StoreAssociation.xml
214 | _pkginfo.txt
215 | *.appx
216 | *.appxbundle
217 | *.appxupload
218 |
219 | # Visual Studio cache files
220 | # files ending in .cache can be ignored
221 | *.[Cc]ache
222 | # but keep track of directories ending in .cache
223 | !?*.[Cc]ache/
224 |
225 | # Others
226 | ClientBin/
227 | ~$*
228 | *~
229 | *.dbmdl
230 | *.dbproj.schemaview
231 | *.jfm
232 | *.pfx
233 | *.publishsettings
234 | orleans.codegen.cs
235 |
236 | # Including strong name files can present a security risk
237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
238 | #*.snk
239 |
240 | # Since there are multiple workflows, uncomment next line to ignore bower_components
241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242 | #bower_components/
243 |
244 | # RIA/Silverlight projects
245 | Generated_Code/
246 |
247 | # Backup & report files from converting an old project file
248 | # to a newer Visual Studio version. Backup files are not needed,
249 | # because we have git ;-)
250 | _UpgradeReport_Files/
251 | Backup*/
252 | UpgradeLog*.XML
253 | UpgradeLog*.htm
254 | ServiceFabricBackup/
255 | *.rptproj.bak
256 |
257 | # SQL Server files
258 | *.mdf
259 | *.ldf
260 | *.ndf
261 |
262 | # Business Intelligence projects
263 | *.rdl.data
264 | *.bim.layout
265 | *.bim_*.settings
266 | *.rptproj.rsuser
267 | *- [Bb]ackup.rdl
268 | *- [Bb]ackup ([0-9]).rdl
269 | *- [Bb]ackup ([0-9][0-9]).rdl
270 |
271 | # Microsoft Fakes
272 | FakesAssemblies/
273 |
274 | # GhostDoc plugin setting file
275 | *.GhostDoc.xml
276 |
277 | # Node.js Tools for Visual Studio
278 | .ntvs_analysis.dat
279 | node_modules/
280 |
281 | # Visual Studio 6 build log
282 | *.plg
283 |
284 | # Visual Studio 6 workspace options file
285 | *.opt
286 |
287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
288 | *.vbw
289 |
290 | # Visual Studio LightSwitch build output
291 | **/*.HTMLClient/GeneratedArtifacts
292 | **/*.DesktopClient/GeneratedArtifacts
293 | **/*.DesktopClient/ModelManifest.xml
294 | **/*.Server/GeneratedArtifacts
295 | **/*.Server/ModelManifest.xml
296 | _Pvt_Extensions
297 |
298 | # Paket dependency manager
299 | .paket/paket.exe
300 | paket-files/
301 |
302 | # FAKE - F# Make
303 | .fake/
304 |
305 | # CodeRush personal settings
306 | .cr/personal
307 |
308 | # Python Tools for Visual Studio (PTVS)
309 | __pycache__/
310 | *.pyc
311 |
312 | # Cake - Uncomment if you are using it
313 | # tools/**
314 | # !tools/packages.config
315 |
316 | # Tabs Studio
317 | *.tss
318 |
319 | # Telerik's JustMock configuration file
320 | *.jmconfig
321 |
322 | # BizTalk build output
323 | *.btp.cs
324 | *.btm.cs
325 | *.odx.cs
326 | *.xsd.cs
327 |
328 | # OpenCover UI analysis results
329 | OpenCover/
330 |
331 | # Azure Stream Analytics local run output
332 | ASALocalRun/
333 |
334 | # MSBuild Binary and Structured Log
335 | *.binlog
336 |
337 | # NVidia Nsight GPU debugger configuration file
338 | *.nvuser
339 |
340 | # MFractors (Xamarin productivity tool) working folder
341 | .mfractor/
342 |
343 | # Local History for Visual Studio
344 | .localhistory/
345 |
346 | # BeatPulse healthcheck temp database
347 | healthchecksdb
348 |
349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
350 | MigrationBackup/
351 |
352 | # Ionide (cross platform F# VS Code tools) working folder
353 | .ionide/
354 | MSEdgeBot/TelegramSettings/SecretKeys.cs
355 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Classes/EdgeFiles.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace EdgeUpdateAPI.Classes
6 | {
7 | public class EdgeFiles
8 | {
9 | public string Version { get; set; }
10 | public EdgeFile[] EdgeFile { get; set; }
11 | }
12 |
13 | public class EdgeFile
14 | {
15 | ///
16 | /// Gets the SHA1 hash in byte array
17 | ///
18 | public byte[] Sha1 { get; set; }
19 |
20 | ///
21 | /// Gets the SHA256 hash in byte array
22 | ///
23 | public byte[] Sha256 { get; set; }
24 |
25 | ///
26 | /// Gets the download url
27 | ///
28 | public string Url { get; set; }
29 |
30 | ///
31 | /// Gets the architecture of the file
32 | ///
33 | public Arch Arch { get; set; }
34 |
35 | ///
36 | /// Gets the file size in bytes
37 | ///
38 | public long Size { get; set; }
39 |
40 | ///
41 | /// Gets the filename
42 | ///
43 | public string FileName { get; set; }
44 |
45 | ///
46 | /// Gets the update type
47 | ///
48 | public EdgeFileUpdateType EdgeFileUpdateType { get; set; }
49 |
50 | ///
51 | /// Gets the version of the delta update if EdgeFileUpdateType == EdgeFileUpdateType.Delta, otherwise null
52 | ///
53 | public string DeltaVersion { get; set; }
54 | }
55 |
56 | public enum EdgeFileUpdateType
57 | {
58 | Delta,
59 | Full
60 | }
61 |
62 | public enum Arch
63 | {
64 | X86,
65 | X64,
66 | ARM64,
67 | ARM
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Classes/Result/IResult.cs:
--------------------------------------------------------------------------------
1 | namespace EdgeUpdateAPI.Classes
2 | {
3 | public interface IResult
4 | {
5 | bool Success { get; }
6 | T Value { get; }
7 | string Message { get; }
8 | ResultType ResultType { get; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Classes/Result/Result.cs:
--------------------------------------------------------------------------------
1 | namespace EdgeUpdateAPI.Classes
2 | {
3 | public class Result : IResult
4 | {
5 |
6 | public Result(bool success, T value)
7 | {
8 | Success = success;
9 | Value = value;
10 | }
11 |
12 | public Result(bool success, string message, ResultType result)
13 | {
14 | Success = success;
15 | ResultType = result;
16 | Message = message;
17 | }
18 |
19 | public Result(bool success, T value, ResultType result)
20 | {
21 | Success = success;
22 | Value = value;
23 | ResultType = result;
24 | }
25 |
26 | public bool Success { get; }
27 | public T Value { get; }
28 | public string Message { get; }
29 | public ResultType ResultType { get; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Classes/Result/ResultType.cs:
--------------------------------------------------------------------------------
1 | namespace EdgeUpdateAPI.Classes
2 | {
3 | public enum ResultType
4 | {
5 | Success, //Default
6 | Unauthorized,
7 | Exception,
8 | Other
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/EdgeUpdate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Utf8Json;
3 | using System.Net;
4 | using System.Text;
5 | using System.Net.Http;
6 | using EdgeUpdateAPI.Classes;
7 | using System.Threading.Tasks;
8 | using EdgeUpdateAPI.Responses;
9 | using Utf8Json.Formatters;
10 | using System.Collections.Generic;
11 | using System.Linq;
12 |
13 | namespace EdgeUpdateAPI
14 | {
15 | public sealed class EdgeUpdate
16 | {
17 | #region Properties
18 |
19 | [Obsolete("Outdated, this will return an old update")]
20 | public static Guid CanaryGuid { get; } = new Guid("65C35B14-6C1D-4122-AC46-7148CC9D6497");
21 | [Obsolete("Outdated, this will return an old update")]
22 | public static Guid DevGuid { get; } = new Guid("0D50BFEC-CD6A-4F9A-964C-C7416E3ACB10");
23 | [Obsolete("Outdated, this will return an old update")]
24 | public static Guid BetaGuid { get; } = new Guid("2CD8A007-E189-409D-A2C8-9AF4EF3C72AA");
25 | [Obsolete("Outdated, this will return an old update")]
26 | public static Guid StableGuid { get; } = new Guid("56EB18F8-B008-4CBD-B6D2-8C97FE7E9062");
27 | [Obsolete("Outdated, this will return an old update")]
28 | public static Guid SetupGuid { get; } = new Guid("F3C4FE00-EFD5-403B-9569-398A20F1BA4A");
29 |
30 | public static string Canary_Win_x64 { get; } = "msedge-canary-win-x64";
31 | public static string Canary_Win_x86 { get; } = "msedge-canary-win-x86";
32 | public static string Canary_Win_arm64 { get; } = "msedge-canary-win-arm64";
33 |
34 | public static string Beta_Win_x64 { get; } = "msedge-beta-win-x64";
35 | public static string Beta_Win_x86 { get; } = "msedge-beta-win-x86";
36 | public static string Beta_Win_arm64 { get; } = "msedge-beta-win-arm64";
37 |
38 | public static string Stable_Win_x64 { get; } = "msedge-stable-win-x64";
39 | public static string Stable_Win_x86 { get; } = "msedge-stable-win-x86";
40 | public static string Stable_Win_arm64 { get; } = "msedge-stable-win-arm64";
41 |
42 | public static string Dev_Win_x64 { get; } = "msedge-dev-win-x64";
43 | public static string Dev_Win_x86 { get; } = "msedge-dev-win-x86";
44 | public static string Dev_Win_arm64 { get; } = "msedge-dev-win-arm64";
45 |
46 | #endregion
47 |
48 | #region Fields
49 |
50 | private readonly HttpClient _hc;
51 | private const string URL = "msedge.api.cdp.microsoft.com";
52 |
53 | #endregion
54 |
55 | //Ctor
56 | public EdgeUpdate()
57 | {
58 | //GZip or deflate.
59 | var filter = new HttpClientHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.All };
60 |
61 | //Create a new instance of HttpClient.
62 | _hc = new HttpClient(filter);
63 | _hc.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Microsoft Edge Update/1.3.101.7;winhttp"); //Not needed but... who cares
64 | _hc.DefaultRequestHeaders.Connection.Add("keep-alive");
65 | }
66 |
67 | #region Public methods
68 |
69 | public async Task> GetLatestFiles(string name)
70 | {
71 | var res = await GetLatestVersion(name);
72 | if (res.Success)
73 | {
74 | return await GetFiles(name, res.Value.ContentId.Version);
75 | }
76 | else
77 | {
78 | return new Result(false, res.Message, res.ResultType);
79 | }
80 | }
81 |
82 | public async Task> GetFiles(string name, string version)
83 | {
84 | var res2 = await GetGeneratedLink(name, version);
85 | if (res2.Success)
86 | {
87 | EdgeFiles edgeFiles = new EdgeFiles
88 | {
89 | Version = version
90 | };
91 |
92 | if (res2.Value.Count > 0)
93 | {
94 | edgeFiles.EdgeFile = new EdgeFile[res2.Value.Count];
95 |
96 | for (int i = 0; i < res2.Value.Count; i++)
97 | {
98 | //if it's a full update == 2 underscores
99 | //3 underscores == delta update
100 |
101 | var splitted = res2.Value[i].FileId.Replace(".exe", "", StringComparison.InvariantCultureIgnoreCase).Split('_');
102 | bool isDeltaUpdate = splitted.Length >= 4;
103 |
104 | //var edgeName = splitted[0];
105 | //var arch = splitted[1];
106 | //var version = splitted[2];
107 | //var deltaUpdate = splitted[3];
108 |
109 | edgeFiles.EdgeFile[i] = new EdgeFile
110 | {
111 | FileName = res2.Value[i].FileId,
112 | Sha1 = Convert.FromBase64String(res2.Value[i].Hashes.Sha1),
113 | Sha256 = Convert.FromBase64String(res2.Value[i].Hashes.Sha256),
114 | Size = res2.Value[i].SizeInBytes,
115 | Url = res2.Value[i].Url,
116 | EdgeFileUpdateType = isDeltaUpdate ? EdgeFileUpdateType.Delta : EdgeFileUpdateType.Full,
117 | DeltaVersion = isDeltaUpdate ? splitted[3] : null,
118 | Arch = Enum.Parse(splitted[1], true)
119 | };
120 | }
121 |
122 | return new Result(true, edgeFiles);
123 | }
124 | else
125 | {
126 | return new Result(false, "Missing files", ResultType.Other);
127 | }
128 | }
129 | else
130 | {
131 | return new Result(false, res2.Message, res2.ResultType);
132 | }
133 | }
134 |
135 | #endregion
136 |
137 | //ABSTRACTION LEVEL OVER 9000
138 |
139 | #region Private methods
140 |
141 | private async Task> GetLatestFiles(Guid guidRing)
142 | => await GetLatestFiles(guidRing.ToString());
143 |
144 | private async Task> GetLatestVersion(string name)
145 | => await SendRequestAndDeserialize($"api/v1/contents/Browser/namespaces/Default/names/{name}/versions/latest?action=select",
146 | HttpMethod.Post, "{\"targetingAttributes\":{}}");
147 |
148 | private async Task> GetLatestVersion(Guid guidRing)
149 | => await GetLatestVersion(guidRing.ToString());
150 |
151 | private async Task>> GetVersionDetails(string name, string version)
152 | => await SendRequestAndDeserialize>($"api/v1/contents/Browser/namespaces/Default/names/{name}/versions/{version}/files",
153 | HttpMethod.Get);
154 | private async Task>> GetVersionDetails(Guid guidRing, string version)
155 | => await GetVersionDetails(guidRing.ToString(), version);
156 |
157 | private async Task>> GetGeneratedLink(string name, string version)
158 | => await SendRequestAndDeserialize>($"api/v1/contents/Browser/namespaces/Default/names/{name}/versions/{version}/files?action=GenerateDownloadInfo",
159 | HttpMethod.Post, "{\"targetingAttributes\":{}}");
160 | private async Task>> GetGeneratedLink(Guid guidRing, string version)
161 | => await GetGeneratedLink(guidRing.ToString(), version);
162 |
163 | private async Task> SendRequestAndDeserialize(string path, HttpMethod reqMethod, string content = "")
164 | {
165 | var req = await SendRequest(path, reqMethod, content);
166 |
167 | if (req.Success)
168 | {
169 | try
170 | {
171 | var serialized = JsonSerializer.Deserialize(req.Value);
172 | return new Result(true, serialized);
173 | }
174 | catch (Exception ex)
175 | {
176 | return new Result(false, ex.Message, ResultType.Exception);
177 | }
178 | }
179 | else
180 | return new Result(false, "Request failure.\n" + req.Message, req.ResultType);
181 |
182 | }
183 |
184 | private async Task> SendRequest(string path, HttpMethod reqMethod, string content = "")
185 | {
186 | var req = CreateRequestHeader(reqMethod, $"https://{URL}/{path}");
187 |
188 | if (reqMethod == HttpMethod.Post)
189 | req.Content = new StringContent(content, Encoding.UTF8, "application/json");
190 |
191 | try
192 | {
193 | var response = await _hc.SendAsync(req);
194 |
195 | if (response.IsSuccessStatusCode)
196 | return new Result(true, await response.Content.ReadAsStringAsync());
197 | else
198 | return new Result(false, "BOH", ResultType.Other); //TODO: replace BOH with something more useful
199 |
200 | }
201 | catch (Exception ex)
202 | {
203 | return new Result(false, ex.Message, ResultType.Exception);
204 | }
205 | }
206 |
207 | private static HttpRequestMessage CreateRequestHeader(HttpMethod method, string url)
208 | => new HttpRequestMessage(method, new Uri(url));
209 |
210 | #endregion
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/EdgeUpdateAPI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | netcoreapp3.1
6 |
7 |
8 | Debug;Release;SHIP_RELEASE
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Responses/FilesResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 | using System.Text;
5 |
6 | namespace EdgeUpdateAPI.Responses
7 | {
8 | public class FilesResponse
9 | {
10 | [DataMember(Name = "FileId", IsRequired = false)]
11 | public string FileId { get; set; }
12 |
13 | [DataMember(Name = "SizeInBytes", IsRequired = false)]
14 | public long SizeInBytes { get; set; }
15 |
16 | [DataMember(Name = "TimeLimitedUrl", IsRequired = false)]
17 | public bool? TimeLimitedUrl { get; set; }
18 |
19 | [DataMember(Name = "Url", IsRequired = false)]
20 | public string Url { get; set; }
21 |
22 | [DataMember(Name = "Hashes", IsRequired = false)]
23 | public Hashes Hashes { get; set; }
24 |
25 | [DataMember(Name = "DeliveryOptimization", IsRequired = false)]
26 | public DeliveryOptimization DeliveryOptimization { get; set; }
27 | }
28 |
29 | public class DeliveryOptimization
30 | {
31 | [DataMember(Name = "PiecesHash", IsRequired = false)]
32 | public PiecesHash PiecesHash { get; set; }
33 | }
34 |
35 | public class PiecesHash
36 | {
37 | [DataMember(Name = "Url")]
38 | public object Url { get; set; }
39 |
40 | [DataMember(Name = "SizeInBytes", IsRequired = false)]
41 | public long? SizeInBytes { get; set; }
42 |
43 | [DataMember(Name = "Hashes")]
44 | public object Hashes { get; set; }
45 |
46 | [DataMember(Name = "HashOfHashes", IsRequired = false)]
47 | public string HashOfHashes { get; set; }
48 | }
49 |
50 | public class Hashes
51 | {
52 | [DataMember(Name = "Sha1", IsRequired = false)]
53 | public string Sha1 { get; set; }
54 |
55 | [DataMember(Name = "Sha256", IsRequired = false)]
56 | public string Sha256 { get; set; }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/EdgeUpdateAPI/Responses/LatestResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace EdgeUpdateAPI.Responses
4 | {
5 | public class LatestResponse
6 | {
7 | [DataMember(Name = "ContentId", IsRequired = false)]
8 | public ContentId ContentId { get; set; }
9 |
10 | [DataMember(Name = "Files", IsRequired = false)]
11 | public string[] Files { get; set; }
12 | }
13 |
14 | public class ContentId
15 | {
16 | [DataMember(Name = "Namespace", IsRequired = false)]
17 | public string Namespace { get; set; }
18 |
19 | [DataMember(Name = "Name", IsRequired = false)]
20 | public string Name { get; set; }
21 |
22 | [DataMember(Name = "Version", IsRequired = false)]
23 | public string Version { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 ADeltaX
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MSEdgeBot.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28714.193
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSEdgeBot", "MSEdgeBot\MSEdgeBot.csproj", "{60E90097-C2FD-4E4B-B778-6E3F9D6FD636}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeUpdateAPI", "EdgeUpdateAPI\EdgeUpdateAPI.csproj", "{CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | PROD_RELEASE|Any CPU = PROD_RELEASE|Any CPU
14 | Release|Any CPU = Release|Any CPU
15 | EndGlobalSection
16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
17 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.PROD_RELEASE|Any CPU.ActiveCfg = SHIP_RELEASE|Any CPU
20 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.PROD_RELEASE|Any CPU.Build.0 = SHIP_RELEASE|Any CPU
21 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {60E90097-C2FD-4E4B-B778-6E3F9D6FD636}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.PROD_RELEASE|Any CPU.ActiveCfg = SHIP_RELEASE|Any CPU
26 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.PROD_RELEASE|Any CPU.Build.0 = SHIP_RELEASE|Any CPU
27 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {CEB6C8C6-E510-41F5-BB19-0004EB5FC8D2}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {1AFA4620-3371-493F-B561-1DFC878B1251}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/MSEdgeBot/Classes/Automation/SearchAutomation.cs:
--------------------------------------------------------------------------------
1 | using EdgeUpdateAPI;
2 | using EdgeUpdateAPI.Classes;
3 | using MSEdgeBot.Classes.Helpers;
4 | using MSEdgeBot.DataStore;
5 | using System;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net;
9 | using System.Security.Cryptography;
10 | using System.Threading.Tasks;
11 | using System.Timers;
12 |
13 | namespace MSEdgeBot.Classes.Automation
14 | {
15 | public static class SearchAutomation
16 | {
17 | private const string messageEdgeUpdate = @"📣 New update in {0}
18 | 📜 Version {1}
19 |
20 | SHA1 {2}
";
21 |
22 | public static bool IsRunning = false;
23 |
24 | //oh c'mon, if it fails more than 11 times (and we are online) == someone's server sucks (or .NET sucks, but I doubt it)
25 | private const int _numMaxDLAttempts = 12;
26 | private static Timer _timerUpdates;
27 |
28 | private static readonly EdgeUpdate _edgeUpdate = new EdgeUpdate();
29 | private static readonly string DownloadFolder = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "dw";
30 |
31 | private static async Task SearchRun(string name, string ringName)
32 | {
33 | var res = await _edgeUpdate.GetLatestFiles(name);
34 | if (res.Success && !SharedDBcmd.UpdateExists(ringName, res.Value.Version))
35 | {
36 | Console.ForegroundColor = ConsoleColor.DarkCyan;
37 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] !!! New update !!! {ringName} - Version: {res.Value.Version}");
38 | Console.ResetColor();
39 |
40 | var edgeFileFull = res.Value.EdgeFile.Where(file => file.EdgeFileUpdateType == EdgeFileUpdateType.Full).First();
41 | var SHA1Hex = BitConverter.ToString(edgeFileFull.Sha1).Replace("-", "");
42 | var SHA256Hex = BitConverter.ToString(edgeFileFull.Sha256).Replace("-", "");
43 |
44 | SharedDBcmd.AddNewUpdate(ring: ringName, version: res.Value.Version,
45 | filename: edgeFileFull.FileName, filesize: edgeFileFull.Size,
46 | sha256: SHA256Hex, url: edgeFileFull.Url);
47 |
48 | Console.ForegroundColor = ConsoleColor.Magenta;
49 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] <<< Downloading from MSEdge fe3 server");
50 | Console.ResetColor();
51 | await Download(name, res.Value.Version, edgeFileFull);
52 |
53 | Console.ForegroundColor = ConsoleColor.Green;
54 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] >>> Uploading to Telegram via TDLib");
55 | Console.ResetColor();
56 | await UploadAndSendMessage(edgeFileFull, string.Format(messageEdgeUpdate, ringName, res.Value.Version, SHA1Hex));
57 |
58 | }
59 | }
60 |
61 | private static async Task UploadAndSendMessage(EdgeFile fileElement, string captionHtml)
62 | {
63 | string file = Path.Combine(DownloadFolder, fileElement.FileName);
64 |
65 | if (!File.Exists(file))
66 | {
67 | SharedDBcmd.TraceError($"Internal error: File is not downloaded => " + file);
68 | Console.WriteLine("File is not downloaded => " + file);
69 | return;
70 | }
71 |
72 | await UploadFileHelper.UploadFileAsync(file, captionHtml);
73 |
74 | }
75 |
76 | private static async Task Download(string name, string version, EdgeFile fileElement, int numTries = 0)
77 | {
78 | if (numTries >= _numMaxDLAttempts)
79 | return;
80 |
81 | try
82 | {
83 | string filename = Path.Combine(DownloadFolder, fileElement.FileName);
84 |
85 | if (File.Exists(filename))
86 | {
87 | byte[] fileHash = GetSHA1HashFile(filename);
88 |
89 | //We already have this update, no need to download it again
90 | if (fileHash.SequenceEqual(fileElement.Sha1))
91 | {
92 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] --- The file is already downloaded and valid, using this...");
93 | return;
94 | }
95 | else
96 | {
97 | File.Delete(filename);
98 | }
99 | }
100 |
101 | using (WebClient wb = new WebClient())
102 | {
103 | await wb.DownloadFileTaskAsync(fileElement.Url, filename);
104 | byte[] fileHash = GetSHA1HashFile(filename);
105 |
106 | if (!fileHash.SequenceEqual(fileElement.Sha1))
107 | {
108 | if (File.Exists(filename))
109 | File.Delete(filename);
110 |
111 | throw new DataMisalignedException("Rip SHA1 hash");
112 | }
113 | }
114 | }
115 | catch (Exception ex)
116 | {
117 | var errorMessageIter = ex.Message;
118 |
119 | var currException = ex;
120 |
121 | while ((currException = currException.InnerException) != null)
122 | errorMessageIter += "\n\nINNER EXCEPTION: " + currException.Message;
123 |
124 | SharedDBcmd.TraceError($"Internal error: {errorMessageIter}");
125 |
126 | Console.WriteLine("[ERROR] Failed downloading... retrying...");
127 |
128 | //Generate a new link.... just in case
129 | var res = await _edgeUpdate.GetFiles(name, version);
130 |
131 | if (res.Success)
132 | {
133 | var edgeFileFull = res.Value.EdgeFile.Where(file => file.EdgeFileUpdateType == EdgeFileUpdateType.Full).First();
134 | await Download(name, version, edgeFileFull, ++numTries);
135 | }
136 | else
137 | {
138 | await Download(name, version, fileElement, ++numTries);
139 | }
140 | }
141 | }
142 |
143 | public static byte[] GetSHA1HashFile(string fileLocation)
144 | {
145 | try
146 | {
147 | using (FileStream fs = new FileStream(fileLocation, FileMode.Open, FileAccess.Read))
148 | using (var cryptoProvider = new SHA1CryptoServiceProvider())
149 | return cryptoProvider.ComputeHash(fs);
150 | }
151 | catch (Exception)
152 | {
153 | return null;
154 | }
155 | }
156 |
157 | public static bool Execute()
158 | {
159 | if (IsRunning)
160 | return false;
161 |
162 | try
163 | {
164 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] Doing searches.");
165 |
166 | IsRunning = true;
167 |
168 | SearchRun(EdgeUpdate.Canary_Win_x86, "Canary (x86)").GetAwaiter().GetResult();
169 | SearchRun(EdgeUpdate.Canary_Win_x64, "Canary (x64)").GetAwaiter().GetResult();
170 | SearchRun(EdgeUpdate.Canary_Win_arm64, "Canary (ARM64)").GetAwaiter().GetResult();
171 |
172 | SearchRun(EdgeUpdate.Dev_Win_x86, "Dev (x86)").GetAwaiter().GetResult();
173 | SearchRun(EdgeUpdate.Dev_Win_x64, "Dev (x64)").GetAwaiter().GetResult();
174 | SearchRun(EdgeUpdate.Dev_Win_arm64, "Dev (ARM64)").GetAwaiter().GetResult();
175 |
176 | SearchRun(EdgeUpdate.Beta_Win_x86, "Beta (x86)").GetAwaiter().GetResult();
177 | SearchRun(EdgeUpdate.Beta_Win_x64, "Beta (x64)").GetAwaiter().GetResult();
178 | SearchRun(EdgeUpdate.Beta_Win_arm64, "Beta (ARM64)").GetAwaiter().GetResult();
179 |
180 | SearchRun(EdgeUpdate.Stable_Win_x86, "Stable (x86)").GetAwaiter().GetResult();
181 | SearchRun(EdgeUpdate.Stable_Win_x64, "Stable (x64)").GetAwaiter().GetResult();
182 | SearchRun(EdgeUpdate.Stable_Win_arm64, "Stable (ARM64)").GetAwaiter().GetResult();
183 |
184 | IsRunning = false;
185 |
186 | Console.WriteLine($"[{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")}] Finished doing searches.");
187 | return true;
188 | }
189 | catch (Exception ex)
190 | {
191 | //RIP
192 | Console.WriteLine($"[Error] {ex.Message}");
193 | SharedDBcmd.TraceError($"Internal error: {ex.Message}");
194 | IsRunning = false;
195 | return false;
196 | }
197 | }
198 |
199 | public static void PreExecute()
200 | {
201 | //Launch an immediate search
202 | Execute();
203 |
204 | var periodTimeSpan = TimeSpan.FromSeconds(60);
205 | _timerUpdates = new Timer();
206 | _timerUpdates.Interval = periodTimeSpan.TotalMilliseconds;
207 | _timerUpdates.Elapsed += (s, e) => {
208 | Console.WriteLine("Standard period-triggered scan");
209 | Execute();
210 | };
211 | _timerUpdates.Start();
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/MSEdgeBot/Classes/Helpers/UploadFileHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace MSEdgeBot.Classes.Helpers
5 | {
6 | public static class UploadFileHelper
7 | {
8 | static int numMaxTries = 6;
9 | public static async Task UploadFileAsync(string path, string captionHtml, int numTries = 0)
10 | {
11 | if (numTries >= numMaxTries)
12 | return;
13 |
14 | try
15 | {
16 | await TDLibHost.TDLibHostBot.UploadFileAndMessage(path, captionHtml);
17 | }
18 | catch (Exception)
19 | {
20 | try
21 | {
22 | await Task.Delay(2000);
23 |
24 | await TDLibHost.TDLibHostBot.Close();
25 |
26 | await Task.Delay(1000);
27 | }
28 | catch (Exception ex)
29 | {
30 | Console.WriteLine("TDLIB PLEASE -> " + ex.Message);
31 | }
32 |
33 | try
34 | {
35 | await TDLibHost.TDLibHostBot.CreateInstance();
36 | }
37 | catch (Exception ex)
38 | {
39 | Console.WriteLine("TDLIB PLEASE - PART 2 -> " + ex.Message);
40 | }
41 |
42 | await UploadFileAsync(path, captionHtml, ++numTries);
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/MSEdgeBot/DataStore/DBEngine.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.Sqlite;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace MSEdgeBot.DataStore
7 | {
8 | internal sealed class DBEngine
9 | {
10 | private readonly SqliteConnection _mDbConnection;
11 |
12 | internal DBEngine()
13 | {
14 | if (!System.IO.File.Exists(TelegramBotSettings.DATABASE_FILENAME))
15 | System.IO.File.Create(TelegramBotSettings.DATABASE_FILENAME).Close();
16 |
17 | _mDbConnection = new SqliteConnection($"Data Source={TelegramBotSettings.DATABASE_FILENAME};");
18 | _mDbConnection.Open();
19 | Console.WriteLine("DB Connection opened!");
20 |
21 | //creazione tabelle
22 | RunCommand(@"CREATE TABLE IF NOT EXISTS Updates (id INTEGER PRIMARY KEY, Ring NVARCHAR NOT NULL, Version NVARCHAR NOT NULL, FileName NVARCHAR NOT NULL, FileSize INTEGER, SHA256 TEXT, Url TEXT, AddedAt DATETIME);
23 | CREATE TABLE IF NOT EXISTS Errors(idError INTEGER PRIMARY KEY, mDateTime datetime NOT NULL, errorMessage NVARCHAR);");
24 | }
25 |
26 | internal void RunCommand(string sql, params KeyValuePair[] parameteres)
27 | {
28 | try
29 | {
30 | using (var command = new SqliteCommand(sql, _mDbConnection))
31 | {
32 | foreach (var param in parameteres)
33 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
34 |
35 | command.ExecuteNonQuery();
36 | }
37 | }
38 | catch (Exception ex)
39 | {
40 | Console.WriteLine(ex.Message);
41 | Console.WriteLine("Ovvero: " + sql);
42 | }
43 | }
44 |
45 | internal bool GetBoolCommand(string sql, params KeyValuePair[] parameteres)
46 | {
47 | try
48 | {
49 | using (var command = new SqliteCommand(sql, _mDbConnection))
50 | {
51 | foreach (var param in parameteres)
52 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
53 |
54 | using (var reader = command.ExecuteReader())
55 | while (reader.Read())
56 | return reader.GetBoolean(0);
57 | }
58 |
59 | return false;
60 | }
61 | catch (Exception ex)
62 | {
63 | Console.WriteLine(ex.Message);
64 | return false;
65 | }
66 | }
67 |
68 | internal string GetStringCommand(string sql, params KeyValuePair[] parameteres)
69 | {
70 | try
71 | {
72 | using (var command = new SqliteCommand(sql, _mDbConnection))
73 | {
74 | foreach (var param in parameteres)
75 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
76 |
77 | using (var reader = command.ExecuteReader())
78 | while (reader.Read())
79 | return reader.GetString(0);
80 | }
81 |
82 | return null;
83 | }
84 | catch (Exception ex)
85 | {
86 | Console.WriteLine(ex.Message);
87 | return null;
88 | }
89 | }
90 |
91 | internal int GetIntCommand(string sql, params KeyValuePair[] parameteres)
92 | {
93 | try
94 | {
95 | using (var command = new SqliteCommand(sql, _mDbConnection))
96 | {
97 | foreach (var param in parameteres)
98 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
99 |
100 | using (var reader = command.ExecuteReader())
101 | while (reader.Read())
102 | return reader.GetInt32(0);
103 | }
104 |
105 | return -1;
106 | }
107 | catch (Exception ex)
108 | {
109 | Console.WriteLine(ex.Message);
110 | return -1;
111 | }
112 | }
113 |
114 | internal List GetListIntCommand(string sql, params KeyValuePair[] parameteres)
115 | {
116 | List ints = new List();
117 |
118 | try
119 | {
120 | using (var command = new SqliteCommand(sql, _mDbConnection))
121 | {
122 | foreach (var param in parameteres)
123 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
124 |
125 | using (var reader = command.ExecuteReader())
126 | while (reader.Read())
127 | ints.Add(reader.GetInt32(0));
128 | }
129 |
130 | return ints;
131 | }
132 | catch (Exception ex)
133 | {
134 | Console.WriteLine(ex.Message);
135 | return ints;
136 | }
137 | }
138 |
139 | internal List> GetListKVLongStringCommand(string sql, params KeyValuePair[] parameteres)
140 | {
141 | List> valuesList = new List>();
142 |
143 | try
144 | {
145 | using (var command = new SqliteCommand(sql, _mDbConnection))
146 | {
147 | foreach (var param in parameteres)
148 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
149 |
150 | using (var reader = command.ExecuteReader())
151 | while (reader.Read())
152 | valuesList.Add(new KeyValuePair(reader.GetInt64(0), reader.GetString(1)));
153 | }
154 |
155 | return valuesList;
156 | }
157 | catch (Exception ex)
158 | {
159 | Console.WriteLine(ex.Message);
160 | return valuesList;
161 | }
162 | }
163 |
164 | internal List> GetListTupleStringCommand(string sql, params KeyValuePair[] parameteres)
165 | {
166 | List> valuesList = new List>();
167 |
168 | try
169 | {
170 | using (var command = new SqliteCommand(sql, _mDbConnection))
171 | {
172 | foreach (var param in parameteres)
173 | command.Parameters.Add(new SqliteParameter(param.Key, param.Value));
174 |
175 | using (var reader = command.ExecuteReader())
176 | while (reader.Read())
177 | valuesList.Add((reader.GetString(0), reader.GetString(1), reader.GetString(2)));
178 | }
179 |
180 | return valuesList;
181 | }
182 | catch (Exception ex)
183 | {
184 | Console.WriteLine(ex.Message);
185 | return valuesList;
186 | }
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/MSEdgeBot/DataStore/SharedDBcmd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 | using System.Text;
5 |
6 | namespace MSEdgeBot.DataStore
7 | {
8 | internal static class SharedDBcmd
9 | {
10 | readonly static DBEngine _db;
11 | static SharedDBcmd() => _db = Program.Db;
12 |
13 | public static void TraceError(string errorMessage, [CallerMemberName] string memberName = "")
14 | => _db.RunCommand("INSERT INTO Errors(mDateTime, errorMessage) VALUES (datetime('now'), @errorMessage)", new KeyValuePair("errorMessage", errorMessage + " | ON " + memberName + "()"));
15 |
16 | public static bool UpdateExists(string ring, string version)
17 | => _db.GetBoolCommand("SELECT CASE WHEN EXISTS (SELECT * FROM Updates WHERE Ring = @ring AND Version = @version) THEN CAST (1 AS BIT) ELSE CAST (0 AS BIT) END", new KeyValuePair("ring", ring), new KeyValuePair("version", version));
18 |
19 | public static void AddNewUpdate(string ring, string version, string filename, long filesize, string sha256, string url)
20 | => _db.RunCommand("INSERT INTO Updates(Ring, Version, FileName, FileSize, SHA256, Url, AddedAt) VALUES (@ring, @version, @filename, @filesize, @sha256, @url, @addedat)", new KeyValuePair("ring", ring), new KeyValuePair("version", version), new KeyValuePair("filename", filename), new KeyValuePair("filesize", filesize), new KeyValuePair("sha256", sha256), new KeyValuePair("url", url), new KeyValuePair("addedat", DateTime.UtcNow));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/MSEdgeBot/MSEdgeBot.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | 7.3
7 | Debug;Release;SHIP_RELEASE
8 |
9 |
10 |
11 | TRACE;PRODUCTION
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/MSEdgeBot/Program.cs:
--------------------------------------------------------------------------------
1 | using MSEdgeBot.Classes.Automation;
2 | using MSEdgeBot.DataStore;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace MSEdgeBot
7 | {
8 | class Program
9 | {
10 | public static DBEngine Db;
11 |
12 | static async Task Main(string[] args)
13 | {
14 | Console.WriteLine("Freshy EdgeUpdate Bot!");
15 | Console.WriteLine("Release version: " + TelegramBotSettings.BOT_VERSION);
16 |
17 | Console.WriteLine("\nInitializing Directories...");
18 | InitializeDirectory();
19 |
20 | Console.WriteLine("\nInitializing Database...");
21 | Db = new DBEngine();
22 |
23 | Console.WriteLine("\nInitializing TDLIB engine...");
24 | TDLibHost tdHost = new TDLibHost();
25 | Console.WriteLine("\nTDLIB engine ready!");
26 |
27 | Task.Factory.StartNew(o => SearchAutomation.PreExecute(), null, TaskCreationOptions.LongRunning);
28 |
29 | string cmd = "";
30 |
31 | do
32 | {
33 | cmd = Console.ReadKey().KeyChar.ToString().ToLower();
34 | } while (cmd != "q");
35 | }
36 |
37 | private static void InitializeDirectory()
38 | {
39 | foreach (var dir in TelegramBotSettings.DIRS_TO_INITALIZE)
40 | {
41 | try
42 | {
43 | if (!System.IO.Directory.Exists(dir))
44 | System.IO.Directory.CreateDirectory(dir);
45 | }
46 | catch (Exception ex)
47 | {
48 | Console.WriteLine($"[INITIALIZATION ERROR] {ex.Message}");
49 | throw ex;
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/MSEdgeBot/TelegramSettings/TDLibHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using TdLib;
8 | using static TdLib.TdApi;
9 |
10 | namespace MSEdgeBot
11 | {
12 | public class TDLibHost
13 | {
14 | private static TdClient _client;
15 | private static bool _authNeeded;
16 | private static bool _isReady;
17 | private static Chat _thisChat;
18 |
19 | private static readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim();
20 | private static readonly ManualResetEventSlim ReadyEvent = new ManualResetEventSlim();
21 | private static readonly ManualResetEventSlim UploadedEvent = new ManualResetEventSlim();
22 |
23 | public static TDLibHost TDLibHostBot { get; set; }
24 |
25 | public TDLibHost() => CreateInstance().GetAwaiter().GetResult();
26 |
27 | public async Task CreateInstance()
28 | {
29 | TDLibHostBot = this;
30 |
31 | _client = new TdClient();
32 | TdLog.SetVerbosityLevel(0);
33 |
34 | _client.UpdateReceived += async (sender, update) =>
35 | {
36 | switch (update)
37 | {
38 | case Update.UpdateOption option:
39 | await _client.ExecuteAsync(new SetOption
40 | {
41 | DataType = option.DataType,
42 | Extra = option.Extra,
43 | Name = option.Name,
44 | Value = option.Value
45 | });
46 | break;
47 | case Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(AuthorizationState.AuthorizationStateWaitTdlibParameters):
48 | await _client.ExecuteAsync(new SetTdlibParameters
49 | {
50 | Parameters = new TdlibParameters
51 | {
52 | ApiId = SecretKeys.API_ID,
53 | ApiHash = SecretKeys.API_HASH,
54 | ApplicationVersion = "1.0.0",
55 | DeviceModel = "PC",
56 | SystemLanguageCode = "en",
57 | SystemVersion = "Win 10.0",
58 | DatabaseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "tdlib"),
59 | EnableStorageOptimizer = true,
60 | FilesDirectory = Path.Combine(Directory.GetCurrentDirectory(), "tdlib")
61 | }
62 | });
63 | break;
64 | case Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(AuthorizationState.AuthorizationStateWaitEncryptionKey):
65 | await _client.ExecuteAsync(new CheckDatabaseEncryptionKey());
66 | break;
67 | case Update.UpdateAuthorizationState updateAuthorizationState when updateAuthorizationState.AuthorizationState.GetType() == typeof(AuthorizationState.AuthorizationStateWaitCode):
68 | case Update.UpdateAuthorizationState updateAuthorizationState2 when updateAuthorizationState2.AuthorizationState.GetType() == typeof(AuthorizationState.AuthorizationStateWaitPhoneNumber):
69 | _authNeeded = true;
70 | ResetEvent.Set();
71 | break;
72 | case Update.UpdateUser updateUser:
73 | break;
74 | case Update.UpdateConnectionState updateConnectionState when updateConnectionState.State.GetType() == typeof(ConnectionState.ConnectionStateReady):
75 | ResetEvent.Set();
76 | ReadyEvent.Set();
77 | break;
78 |
79 | case Update.UpdateMessageSendFailed uwu:
80 | case Update.UpdateMessageSendSucceeded uwu2:
81 | //what happens ?
82 | //BIG *RIP*
83 | UploadedEvent.Set();
84 | break;
85 |
86 | default:
87 | break;
88 | }
89 | };
90 |
91 | #if !PRODUCTION
92 | await Cont(TelegramBotSettings.DEV_CHANNEL);
93 | #else
94 | await Cont(TelegramBotSettings.PROD_CHANNEL);
95 | #endif
96 | }
97 |
98 | private async Task Cont(string channelName)
99 | {
100 | ResetEvent.Wait();
101 |
102 | if (_authNeeded)
103 | {
104 | await _client.ExecuteAsync(new CheckAuthenticationBotToken
105 | {
106 | #if !PRODUCTION
107 | Token = SecretKeys.DEV_KEY
108 | #else
109 | Token = SecretKeys.PROD_KEY
110 | #endif
111 | });
112 | }
113 |
114 | ReadyEvent.Wait();
115 |
116 | if (_thisChat == null)
117 | _thisChat = await _client.SearchPublicChatAsync(channelName);
118 |
119 | _isReady = true;
120 | }
121 |
122 | public async Task Close()
123 | {
124 | try
125 | {
126 | await _client.CloseAsync();
127 | _client.Dispose();
128 | _isReady = false;
129 | _authNeeded = false;
130 | }
131 | catch (Exception)
132 | {
133 | //ok
134 | }
135 | }
136 |
137 | public async Task UploadFileAndMessage(string pathFileToUpload, string messageHtml)
138 | {
139 | if (_isReady)
140 | {
141 | var capt = await _client.ParseTextEntitiesAsync(messageHtml, new TextParseMode.TextParseModeHTML());
142 |
143 |
144 | await _client.SendMessageAsync(_thisChat.Id, 0, new SendMessageOptions { DisableNotification = true },
145 | null, new InputMessageContent.InputMessageDocument
146 | {
147 | Document = new InputFile.InputFileLocal { Path = pathFileToUpload },
148 | Caption = capt,
149 | });
150 |
151 | UploadedEvent.Wait();
152 |
153 | UploadedEvent.Reset();
154 | }
155 | else
156 | {
157 | throw new InvalidOperationException("Hmm, too early or not connected.");
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/MSEdgeBot/TelegramSettings/TelegramBotSettings.cs:
--------------------------------------------------------------------------------
1 | namespace MSEdgeBot
2 | {
3 | public static class TelegramBotSettings
4 | {
5 | public const string BOT_VERSION = "EdgeBot 1.1";
6 | public static string DATABASE_FILENAME = "DataStore" + System.IO.Path.DirectorySeparatorChar + "EdgeUpdatesDB.sqlite";
7 | public static string[] DIRS_TO_INITALIZE = { "DataStore", "dw", "tdlib" };
8 |
9 | public const string DEV_CHANNEL = SecretKeys.DEV_CHANNEL;
10 | public const string PROD_CHANNEL = SecretKeys.PROD_CHANNEL;
11 |
12 | public const string DEV_KEY = SecretKeys.DEV_KEY;
13 | public const string PROD_KEY = SecretKeys.PROD_KEY;
14 |
15 | public const string API_HASH = SecretKeys.API_HASH;
16 | public const int API_ID = SecretKeys.API_ID;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MSEdgeBot
2 | The bot that empowers the Telegram Channel https://t.me/MSEdgeUpdates, written in C# (.NET Core 3.1), powered by TDLib
3 |
4 | ## How to build
5 | Create a new Class file named "SecretKeys.cs" and put it in MSEdgeBot\TelegramSettings\
6 |
7 | and copy this (and replace it with your values):
8 | ```cs
9 | public static class SecretKeys
10 | {
11 | public const string DEV_CHANNEL = "CHANNEL_NAME";
12 | public const string PROD_CHANNEL = "CHANNEL_NAME";
13 |
14 | public const string DEV_KEY = "TELEGRAM_BOT_TOKEN";
15 | public const string PROD_KEY = "TELEGRAM_BOT_TOKEN";
16 |
17 | public const string API_HASH = "TELEGRAM_MTPROTO_API_HASH";
18 | public const int API_ID = TELEGRAM_MTPROTO_API_ID;
19 | }
20 | ```
21 |
22 | ...and now you can compile! RELEASE_PROD configuration will build as "production" meanwhile other configurations will build as "development"
23 |
24 | Enjoy!
25 |
--------------------------------------------------------------------------------