-
Notifications
You must be signed in to change notification settings - Fork 37
/
ESI.vb
2645 lines (2138 loc) · 119 KB
/
ESI.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Imports System.Net
Imports System.IO
Imports System.Net.Sockets
Imports System.Text
Imports System.Data.SQLite
Imports System.Collections.Specialized
Imports System.Threading
Imports System.Security.Cryptography
Imports Newtonsoft.Json
Public Module ESIGlobals
Public ESICharacterSkillsScope As String = "esi-skills.read_skills" ' only required scope to use IPH
End Module
Public Class ESI
Private Const ESIAuthorizeURL As String = "https://login.eveonline.com/v2/oauth/authorize"
Private Const ESITokenURL As String = "https://login.eveonline.com/v2/oauth/token"
Private Const ESIURL As String = "https://esi.evetech.net/latest/"
Private Const ESIStatusURL As String = "https://esi.evetech.net/status.json?version=latest"
Private Const TranquilityDataSource As String = "?datasource=tranquility"
Private Const LocalHost As String = "127.0.0.1" ' All calls will redirect to local host.
Private Const LocalPort As String = "12500" ' Always use this port
Private Const EVEIPHClientID As String = "2737513b64854fa0a309e125419f8eff" ' IPH Client ID in EVE Developers
Private AuthorizationToken As String ' Token returned by ESI on initial authorization - good for 5 minutes
Private CodeVerifier As String ' For PKCE - generated code we send to ESI for access codes after sending the hashed version of this for authorization code
Public Const ESICharacterAssetScope As String = "esi-assets.read_assets"
Public Const ESICharacterResearchAgentsScope As String = "esi-characters.read_agents_research"
Public Const ESICharacterBlueprintsScope As String = "esi-characters.read_blueprints"
Public Const ESICharacterStandingsScope As String = "esi-characters.read_standings"
Public Const ESICharacterIndustryJobsScope As String = "esi-industry.read_character_jobs"
Public Const ESICharacterSkillsScope As String = "esi-skills.read_skill"
Public Const ESICorporationAssetScope As String = "esi-assets.read_corporation_assets"
Public Const ESICorporationBlueprintsScope As String = "esi-corporations.read_blueprints"
Public Const ESICorporationIndustryJobsScope As String = "esi-industry.read_corporation_jobs"
Public Const ESICorporationMembership As String = "esi-corporations.read_corporation_membership"
Public Const ESIUniverseStructuresScope As String = "esi-universe.read_structures"
Public Const ESIStructureMarketsScope As String = "esi-markets.structure_markets"
'' Rate limits
''For your requests, this means you can send an occasional burst of 400 requests all at once.
''If you do, you'll hit the rate limit once you try to send your 401st request unless you wait.
''Your bucket refills at a rate of 1 per 1/150th of a second. If you send 400 requests at once,
''you need to wait 2.67 seconds before you can send another 400 requests (1/150 * 400), if you
''only wait 1.33 seconds you can send another 200, and so on. Altrnatively, you can send a constant 150 requests every 1 second.
'Private Const ESIRatePerSecond As Integer = 150 ' max requests per second
'Private Const ESIBurstSize As Integer = 400 ' max burst of requests, which need 2.46 seconds to refill before re-bursting
Private Const ESIMaximumConnections As Integer = 20
Private AuthThreadReference As Thread ' Reference in case we need to kill this
Private myListener As TcpListener = New TcpListener(IPAddress.Parse(LocalHost), CInt(LocalPort)) ' Ref to the listener so we can stop it
Private StructureCount As Integer ' for counting order updates
Public Enum ESICallType
Basic = 0
Threaded = 1
End Enum
' ESI implements the following scopes:
' Character
' esi-assets.read_assets.v1: Allows reading a list of assets that the character owns
' esi-characters.read_agents_research.v1: Allows reading a character's research status with agents
' esi-characters.read_blueprints.v1: Allows reading a character's blueprints
' esi-characters.read_standings.v1: Allows reading a character's standings
' esi-industry.read_character_jobs.v1: Allows reading a character's industry jobs
' esi-skills.read_skills.v1: Allows reading of a character's currently known skills.
' Corporation
' esi-assets.read_corporation_assets.v1: Allows reading of a character's corporation's assets, if the character has roles to do so.
' esi-corporations.read_blueprints.v1: Allows reading a corporation's blueprints
' esi-industry.read_corporation_jobs.v1: Allows reading of a character's corporation's industry jobs, if the character has roles to do so.
'
' esi-universe.read_structure.v1: Allows reading of all public structures in the universe
' esi-markets.structure_markets.v1: Allows reading of markets for structures the character can use
''' <summary>
''' Initialize the class variables as needed
''' </summary>
Public Sub New()
AuthorizationToken = ""
CodeVerifier = ""
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
End Sub
''' <summary>
''' Opens a connection to EVE SSO server and gets an authorization token to get an access token
''' </summary>
''' <returns>Authorization Token</returns>
Private Function GetAuthorizationToken() As String
Try
Dim StartTime As Date = Now
' See if we are in an error limited state
If ESIErrorHandler.ErrorLimitReached Then
' Need to wait until we are ready to continue
Call Thread.Sleep(ESIErrorHandler.msErrorTimer)
End If
AuthThreadReference = New Thread(AddressOf GetAuthorizationfromWeb)
AuthThreadReference.Start()
' Reset this to make sure it only comes up if they hit the button
CancelESISSOLogin = False
' Now loop until thread is done, 60 seconds goes by, or cancel clicked
Do
If DateDiff(DateInterval.Second, StartTime, Now) > 60 Then
Call MsgBox("Request timed out. You must complete your login within 60 seconds.", vbInformation, Application.ProductName)
myListener.Stop()
AuthThreadReference.Abort()
Return Nothing
ElseIf CancelESISSOLogin Then
Call MsgBox("Request Canceled", vbInformation, Application.ProductName)
myListener.Stop()
AuthThreadReference.Abort()
Return Nothing
ElseIf Not AuthThreadReference.IsAlive Then
Exit Do
End If
Application.DoEvents()
Loop
' Get the authtoken after it completed
Return AuthorizationToken
Catch ex As WebException
Call ESIErrorHandler.ProcessWebException(ex, ESIErrorProcessor.ESIErrorLocation.AuthToken, False, "")
' Retry call
If ESIErrorHandler.ErrorCode >= 500 And Not ESIErrorHandler.RetriedCall Then
ESIErrorHandler.RetriedCall = True
' Try this call again after waiting a second
Thread.Sleep(1000)
Return GetAuthorizationToken()
End If
Catch ex As Exception
Call ESIErrorHandler.ProcessException(ex, ESIErrorProcessor.ESIErrorLocation.AuthToken, False)
End Try
Return ""
End Function
''' <summary>
''' Opens the login for EVE SSO and returns the data stream from a successful log in
''' </summary>
Private Sub GetAuthorizationfromWeb(Optional MyURL As String = "")
Try
Dim URL As String = ""
Dim ChallengeCode As String = ""
Dim AuthStreamText As String = "" ' The return data from the web call
' State is a random number for passing to ESI
Dim InitState As String = DateTime.UtcNow.ToFileTime().ToString()
' Set the challenge code for this call
CodeVerifier = GetCodeVerifier()
ChallengeCode = GetChallengeCode(CodeVerifier)
' Build the authorization URL
URL = ESIAuthorizeURL
URL &= "?client_id=" & EVEIPHClientID
URL &= "&redirect_uri=" & WebUtility.UrlEncode("http://" & LocalHost & ":" & LocalPort)
URL &= "&response_type=code"
URL &= "&scope=" & WebUtility.UrlEncode(ESIScopesString)
URL &= "&state=" & InitState
URL &= "&code_challenge=" & ChallengeCode
URL &= "&code_challenge_method=S256"
' Open the browser to get authorization from user
Call Process.Start(URL)
Dim mySocket As Socket = Nothing
Dim myStream As NetworkStream = Nothing
Dim myReader As StreamReader = Nothing
Dim myWriter As StreamWriter = Nothing
myListener.Start()
mySocket = myListener.AcceptSocket() ' Wait for response
myStream = New NetworkStream(mySocket)
myReader = New StreamReader(myStream)
myWriter = New StreamWriter(myStream)
myWriter.AutoFlush = True
Do
' Get the response from the callback
AuthStreamText &= myReader.ReadLine & "|"
If AuthStreamText.Contains("code") And AuthStreamText.Contains("state") Then
Exit Do
End If
Loop Until myReader.EndOfStream
' Strip the code and state from the callback stream text
AuthStreamText = AuthStreamText.Substring(InStr(AuthStreamText, "/"))
Dim CodeStart As Integer = 6 '?code=
Dim CodeEnd As Integer = InStr(AuthStreamText, "&state=") - 1
Dim StateStart As Integer = CodeEnd + 7
Dim StateEnd As Integer = InStr(AuthStreamText, " ") - 1
' Parse the values - Check the state return first
Dim ReturnState As String = AuthStreamText.Substring(StateStart, StateEnd - StateStart)
If ReturnState <> InitState Then
myWriter.Write("The Authorization was unsuccessful. Please retry your request." & vbCrLf & vbCrLf & "You can close this window.")
Else
myWriter.Write("Authorization Successful!" & vbCrLf & vbCrLf & "You can close this window.")
End If
' Now save the auth token to get access tokens
AuthorizationToken = AuthStreamText.Substring(CodeStart, CodeEnd - CodeStart)
myWriter.Close()
myReader.Close()
myStream.Close()
mySocket.Close()
myListener.Stop()
Application.DoEvents()
Catch ex As Exception
Application.DoEvents()
End Try
End Sub
''' <summary>
''' Gets the Access Token data from the EVE server for authorization or refresh tokens.
''' </summary>
''' <param name="Token">An Authorization or Refresh Token</param>
''' <param name="Refresh">If the token is Authorization or Refresh</param>
''' <returns>Access Token Data object</returns>
Private Function GetAccessToken(ByVal Token As String, ByVal Refresh As Boolean, ByRef TokenCharacterID As Long, ByRef TokenScopes As String) As ESITokenData
If Token = "No Token" Or Token = "" Then
Return Nothing
End If
Dim Output As New ESITokenData
Dim Success As Boolean = False
Dim WC As New WebClient
Dim Response As Byte()
Dim Data As String = ""
WC.Headers(HttpRequestHeader.ContentType) = "application/x-www-form-urlencoded"
WC.Headers(HttpRequestHeader.Host) = "login.eveonline.com"
WC.Proxy = GetProxyData()
Dim PostParameters As New NameValueCollection
If Not Refresh Then
PostParameters.Add("grant_type", "authorization_code")
PostParameters.Add("code", Token)
PostParameters.Add("client_id", EVEIPHClientID)
PostParameters.Add("code_verifier", CodeVerifier) ' PKCE - Send the code verifier for ESI to hash to compare to what we sent earlier
Else
PostParameters.Add("grant_type", "refresh_token")
PostParameters.Add("refresh_token", Token)
PostParameters.Add("client_id", EVEIPHClientID)
End If
Try
' See if we are in an error limited state
If ESIErrorHandler.ErrorLimitReached Then
' Need to wait until we are ready to continue
Call Thread.Sleep(ESIErrorHandler.msErrorTimer)
End If
' Response in bytes
Response = WC.UploadValues(ESITokenURL, "POST", PostParameters)
' Convert byte data to string
Data = Encoding.UTF8.GetString(Response)
' Parse the data to the ESI Token Data Class to get the JWT Access Code
Output = JsonConvert.DeserializeObject(Of ESITokenData)(Data)
' Split the authorization token into the header + payload + signature
Dim JWTAccessTokenParts As String() = Output.access_token.Split("."c)
Dim Header As String = FormatJWTStringLength(JWTAccessTokenParts(0))
Dim Payload As String = FormatJWTStringLength(JWTAccessTokenParts(1))
Dim Signature As String = JWTAccessTokenParts(2)
Dim TokenHeader As ESIJWTHeader
Dim TokenPayload As ESIJWTPayload
' Convert each part to a byte array, then encode it to a string for JSON parsing
TokenHeader = JsonConvert.DeserializeObject(Of ESIJWTHeader)(Encoding.UTF8.GetString(Convert.FromBase64String(Header)))
TokenPayload = JsonConvert.DeserializeObject(Of ESIJWTPayload)(Encoding.UTF8.GetString(Convert.FromBase64String(Payload)))
' TODO - Valiate signature and token
' Get the character ID from the JWT
Dim SubjectInfo As String() = TokenPayload.Subject.Split(":"c)
TokenCharacterID = CLng(SubjectInfo(2))
'Return the scopes as well
For Each entry In TokenPayload.Scopes
TokenScopes &= entry & " "
Next
TokenScopes = Trim(TokenScopes)
Success = True
Catch ex As WebException
Call ESIErrorHandler.ProcessWebException(ex, ESIErrorProcessor.ESIErrorLocation.AccessToken, False, "")
' Retry call
If ESIErrorHandler.ErrorCode >= 500 And Not ESIErrorHandler.RetriedCall Then
ESIErrorHandler.RetriedCall = True
' Try this call again after waiting a second
Thread.Sleep(1000)
Output = GetAccessToken(Token, Refresh, TokenCharacterID, TokenScopes)
If Not IsNothing(Output) Then
Success = True
End If
End If
Catch ex As Exception
Call ESIErrorHandler.ProcessException(ex, ESIErrorProcessor.ESIErrorLocation.AccessToken, False)
End Try
If Success Then
Return Output
Else
Return Nothing
End If
End Function
Private Shared Function Base64UrlEncode(ByVal input As String) As String
' Dim output = Convert.ToBase64String(input)
Dim output As String = input
output = output.Split("="c)(0)
output = output.Replace("+"c, "-"c)
output = output.Replace("/"c, "_"c)
Return output
End Function
Public Class ESIJWTHeader
<JsonProperty("alg")> Public Algorithm As String
<JsonProperty("kid")> Public KeyID As String
<JsonProperty("typ")> Public TokenType As String
End Class
Public Class ESIJWTPayload
<JsonProperty("scp")> Public Scopes As List(Of String)
<JsonProperty("jti")> Public JWTID As String
<JsonProperty("kid")> Public KeyID As String
<JsonProperty("sub")> Public Subject As String
<JsonProperty("azp")> Public AuthParty As String ' EVEIPH ClientID
<JsonProperty("tenant")> Public Tenant As String
<JsonProperty("tier")> Public Tier As String
<JsonProperty("region")> Public DataRegion As String
<JsonProperty("aud")> Public Audience As List(Of String)
<JsonProperty("name")> Public Name As String
<JsonProperty("owner")> Public Owner As String
<JsonProperty("exp")> Public ExpirationTime As Long ' Unix time stamp
<JsonProperty("iat")> Public IssuedAt As Long ' Unix time stamp
<JsonProperty("iss")> Public Issuer As String ' Who created and signed token
End Class
' Adds spacers (=) to the JWT string portion to be converted to Base 64
Private Function FormatJWTStringLength(JWTStringPortion As String) As String
Dim StrLen As Integer = Len(JWTStringPortion)
Dim CorrectedString As String = JWTStringPortion
' Add spacers until mod 4 returns 0
Do While StrLen Mod 4 <> 0
CorrectedString &= "="
StrLen += 1
Loop
Return CorrectedString
End Function
''' <summary>
''' Queries the server for public data for the URL sent. If not found, returns nothing
''' </summary>
''' <param name="URL">Full public data URL as a string</param>
''' <returns>Byte Array of response or nothing if call fails</returns>
Private Function GetPublicData(ByVal URL As String, ByRef CacheDate As Date, Optional BodyData As String = "") As String
Dim Response As String = ""
Dim WC As New WebClient
Dim myWebHeaderCollection As New WebHeaderCollection
Dim Expires As String = Nothing
Dim Pages As Integer = Nothing
Dim ESIErrorLimitRemain As Integer = -1
Dim ESIErrorLimitReset As Integer = -1
Try
' See if we are in an error limited state
If ESIErrorHandler.ErrorLimitReached Then
' Need to wait until we are ready to continue
Call Thread.Sleep(ESIErrorHandler.msErrorTimer)
End If
WC.Proxy = GetProxyData()
If BodyData <> "" Then
Response = Encoding.UTF8.GetString(WC.UploadData(URL, Encoding.UTF8.GetBytes(BodyData)))
Else
Response = WC.DownloadString(URL)
End If
' Get the expiration date for the cache date
myWebHeaderCollection = WC.ResponseHeaders
Expires = myWebHeaderCollection.Item("Expires")
Pages = CInt(myWebHeaderCollection.Item("X-Pages"))
ESIErrorLimitRemain = CInt(myWebHeaderCollection.Item("X-ESI-Error-Limit-Remain"))
ESIErrorLimitReset = CInt(myWebHeaderCollection.Item("X-ESI-Error-Limit-Reset"))
If Not IsNothing(Expires) Then
CacheDate = CDate(Expires.Replace("GMT", "").Substring(InStr(Expires, ",") + 1)) ' Expiration date is in GMT
Else
CacheDate = NoExpiry
End If
If Not IsNothing(Pages) Then
If Pages > 1 Then
Dim TempResponse As String = ""
For i = 2 To Pages
TempResponse = WC.DownloadString(URL & "&page=" & CStr(i))
' Combine with the original response - strip the end and leading brackets
Response = Response.Substring(0, Len(Response) - 1) & "," & TempResponse.Substring(1)
Next
End If
End If
Return Response
Catch ex As WebException
Call ESIErrorHandler.ProcessWebException(ex, ESIErrorProcessor.ESIErrorLocation.PublicData, UserApplicationSettings.SupressESIStatusMessages, URL)
' Retry call
If ESIErrorHandler.ErrorCode >= 500 And Not ESIErrorHandler.RetriedCall Then
ESIErrorHandler.RetriedCall = True
' Try this call again after waiting a second
Thread.Sleep(1000)
Return GetPublicData(URL, CacheDate, BodyData)
End If
Catch ex As Exception
Call ESIErrorHandler.ProcessException(ex, ESIErrorProcessor.ESIErrorLocation.PublicData, UserApplicationSettings.SupressESIStatusMessages)
End Try
If Not IsNothing(Response) Then
If Response <> "" Then
Return Response
Else
Return Nothing
End If
Else
Return Nothing
End If
End Function
''' <summary>
''' Queries the server for private, authorized data for data sent. Function will check the
''' authorization token and update the sent variable and DB data if expired.
''' </summary>
''' <returns>Returns data response as a string</returns>
Private Function GetPrivateAuthorizedData(ByVal URL As String, ByVal TokenData As ESITokenData,
ByVal TokenExpiration As Date, ByRef CacheDate As Date,
ByVal CharacterID As Long, Optional ByVal SupressErrorMsgs As Boolean = False,
Optional SinglePage As Boolean = False) As String
Dim WC As New WebClient
Dim Response As String = ""
Dim TokenExpireDate As Date
Try
' See if we update the token data first
If TokenExpiration <= DateTime.UtcNow Then
' Update the token
TokenData = GetAccessToken(TokenData.refresh_token, True, Nothing, Nothing)
If IsNothing(TokenData) Then
Return Nothing
End If
' Update the token data in the DB for this character
Dim SQL As String = ""
' Update data - only stuff that could (reasonably) change
SQL = "UPDATE ESI_CHARACTER_DATA SET ACCESS_TOKEN = '{0}', ACCESS_TOKEN_EXPIRE_DATE_TIME = '{1}', "
SQL &= "TOKEN_TYPE = '{2}', REFRESH_TOKEN = '{3}' WHERE CHARACTER_ID = {4}"
With TokenData
TokenExpireDate = DateAdd(DateInterval.Second, .expires_in, DateTime.UtcNow)
SQL = String.Format(SQL, FormatDBString(.access_token),
Format(TokenExpireDate, SQLiteDateFormat),
FormatDBString(.token_type), FormatDBString(.refresh_token), CharacterID)
End With
' If we are in a transaction, we want to commit this so it's up to date, so close and reopen
If EVEDB.TransactionActive Then
EVEDB.CommitSQLiteTransaction()
EVEDB.ExecuteNonQuerySQL(SQL)
EVEDB.BeginSQLiteTransaction()
Else
EVEDB.ExecuteNonQuerySQL(SQL)
End If
' Now update the copy used in IPH so we don't re-query
SelectedCharacter.CharacterTokenData.AccessToken = TokenData.access_token
SelectedCharacter.CharacterTokenData.RefreshToken = TokenData.refresh_token
SelectedCharacter.CharacterTokenData.TokenExpiration = TokenExpireDate
End If
' See if we are in an error limited state
If ESIErrorHandler.ErrorLimitReached Then
' Need to wait until we are ready to continue
Call Thread.Sleep(ESIErrorHandler.msErrorTimer)
End If
Dim Auth_header As String = $"Bearer {TokenData.access_token}"
WC.Proxy = GetProxyData()
WC.Headers(HttpRequestHeader.Authorization) = Auth_header
Response = WC.DownloadString(URL)
' Get the expiration date for the cache date
Dim myWebHeaderCollection As WebHeaderCollection = WC.ResponseHeaders
Dim Expires As String = myWebHeaderCollection.Item("Expires")
Dim Pages As Integer = CInt(myWebHeaderCollection.Item("X-Pages"))
If Not IsNothing(Expires) Then
CacheDate = CDate(Expires.Replace("GMT", "").Substring(InStr(Expires, ",") + 1)) ' Expiration date is in GMT
Else
CacheDate = NoExpiry
End If
If Not IsNothing(Pages) Then
If Pages > 1 Then
Dim TempResponse As String = ""
For i = 2 To Pages
TempResponse = WC.DownloadString(URL & "&page=" & CStr(i))
' Combine with the original response - strip the end and leading brackets
Response = Response.Substring(0, Len(Response) - 1) & "," & TempResponse.Substring(1)
If SinglePage Then
Exit For
End If
Next
End If
End If
Return Response
Catch ex As WebException
Call ESIErrorHandler.ProcessWebException(ex, ESIErrorProcessor.ESIErrorLocation.PrivateAuthData, SupressErrorMsgs, URL)
' Retry call
If ESIErrorHandler.ErrorCode >= 500 And Not ESIErrorHandler.RetriedCall Then
ESIErrorHandler.RetriedCall = True
' Try this call again after waiting a second
Thread.Sleep(1000)
Return GetPrivateAuthorizedData(URL, TokenData, TokenExpiration, CacheDate, CharacterID, SupressErrorMsgs)
End If
Catch ex As Exception
Call ESIErrorHandler.ProcessException(ex, ESIErrorProcessor.ESIErrorLocation.PrivateAuthData, SupressErrorMsgs)
End Try
If Response <> "" Then
Return Response
Else
Return Nothing
End If
End Function
''' <summary>
''' Formats a SavedTokenData object to ESITokenData
''' </summary>
''' <param name="TokenData">SavedTokenData object</param>
''' <returns>the ESITokenData object</returns>
Private Function FormatTokenData(ByVal TokenData As SavedTokenData) As ESITokenData
Dim TempTokenData As New ESITokenData
TempTokenData.access_token = TokenData.AccessToken
TempTokenData.refresh_token = TokenData.RefreshToken
TempTokenData.token_type = TokenData.TokenType
Return TempTokenData
End Function
''' <summary>
''' Formats string dates returned from the ESI server into a date object
''' </summary>
''' <param name="OriginalDate">ESI date value as a string</param>
''' <returns>Date object of the sent date as a string</returns>
Public Function FormatESIDate(ByVal OriginalDate As String) As Date
If Not IsNothing(OriginalDate) Then
Return CDate(OriginalDate.Replace("T", " ").Replace("Z", ""))
Else
Return NoDate
End If
End Function
#Region "Load Character Data"
''' <summary>
''' Gets verification and public data about the character returned from logging into the EVE SSO and authorizing
''' access for a new character first logging in. If the character has already been loaded, then update the data.
''' </summary>
''' <returns>Returns boolean if the function was successful in setting character data.</returns>
Public Function SetCharacterData(ByVal AccountRefresh As Boolean, Optional ByRef CharacterTokenData As SavedTokenData = Nothing,
Optional ByVal ManualAuthToken As String = "",
Optional ByVal IgnoreCacheDate As Boolean = False,
Optional ByVal SupressMessages As Boolean = False,
Optional ByRef CorporationID As Long = -1) As Boolean
Dim TokenData As ESITokenData
Dim CharacterData As New ESICharacterPublicData
Dim CharacterID As Long
Dim Scopes As String = ""
Dim CB As New CacheBox
Dim CacheDate As Date
If Not IsNothing(CharacterTokenData) Then
CharacterID = CharacterTokenData.CharacterID
Else
CharacterID = 0
End If
Try
If CB.DataUpdateable(CacheDateType.PublicCharacterData, CharacterID) Or IgnoreCacheDate Then
If CharacterID = 0 Then
' We need to get the token data from the authorization
If ManualAuthToken <> "" Then
TokenData = GetAccessToken(ManualAuthToken, False, CharacterID, Scopes)
Else
TokenData = GetAccessToken(GetAuthorizationToken(), False, CharacterID, Scopes)
End If
CharacterTokenData = New SavedTokenData
Else
' We need to refresh the token data
TokenData = GetAccessToken(CharacterTokenData.RefreshToken, True, CharacterID, Scopes)
End If
If IsNothing(TokenData) Then
' They tried to refresh from account data, so open the web auth if it's an invalid token
If AccountRefresh Then
' First, reset the scope list based on what they have now
ESIScopesString = CharacterTokenData.Scopes
TokenData = GetAccessToken(GetAuthorizationToken(), False, CharacterID, Scopes)
' Now that we have the token data, make sure they selected the same character as we wanted to update and didn't select a different one
If CharacterID <> CharacterTokenData.CharacterID Then
MsgBox("You must select the same character on the Character Select webpage as the one you select in IPH. Please try again.", vbExclamation, Application.ProductName)
Return False
End If
Else
Return False
End If
End If
' Update the local copy with the new information
CharacterTokenData.AccessToken = TokenData.access_token
CharacterTokenData.RefreshToken = TokenData.refresh_token
CharacterTokenData.TokenType = TokenData.token_type
CharacterTokenData.CharacterID = CharacterID
CharacterTokenData.Scopes = Scopes
' Set the expiration date to pass
CharacterTokenData.TokenExpiration = DateAdd(DateInterval.Second, TokenData.expires_in, DateTime.UtcNow)
CharacterData = GetCharacterPublicData(CharacterTokenData.CharacterID, CacheDate)
If IsNothing(CharacterData) Then
Return False
End If
' Save the corporationID for reference
CorporationID = CharacterData.corporation_id
' Save it in the table if not there, or update it if they selected the character again
Dim rsCheck As SQLiteDataReader
Dim SQL As String
SQL = "SELECT * FROM ESI_CHARACTER_DATA WHERE CHARACTER_ID = " & CStr(CharacterTokenData.CharacterID)
DBCommand = New SQLiteCommand(SQL, EVEDB.DBREf)
rsCheck = DBCommand.ExecuteReader
If rsCheck.HasRows Then
' Update data - only stuff that could (reasonably) change
SQL = "UPDATE ESI_CHARACTER_DATA SET CORPORATION_ID = {0}, DESCRIPTION = '{1}', SCOPES = '{2}', ACCESS_TOKEN = '{3}',"
SQL &= "ACCESS_TOKEN_EXPIRE_DATE_TIME = '{4}', TOKEN_TYPE = '{5}', REFRESH_TOKEN = '{6}' "
SQL &= "WHERE CHARACTER_ID = {7}"
With CharacterData
SQL = String.Format(SQL, .corporation_id,
FormatDBString(FormatNullString(.description)),
FormatDBString(CharacterTokenData.Scopes),
FormatDBString(CharacterTokenData.AccessToken),
Format(CharacterTokenData.TokenExpiration, SQLiteDateFormat),
FormatDBString(CharacterTokenData.TokenType),
FormatDBString(CharacterTokenData.RefreshToken),
CharacterTokenData.CharacterID)
End With
Else
' Insert new data
SQL = "INSERT INTO ESI_CHARACTER_DATA (CHARACTER_ID, CHARACTER_NAME, CORPORATION_ID, BIRTHDAY, GENDER, RACE_ID, "
SQL &= "BLOODLINE_ID, ANCESTRY_ID, DESCRIPTION, ACCESS_TOKEN, ACCESS_TOKEN_EXPIRE_DATE_TIME, TOKEN_TYPE, REFRESH_TOKEN, "
SQL &= "SCOPES, OVERRIDE_SKILLS, IS_DEFAULT)"
SQL &= "VALUES ({0},'{1}',{2},'{3}','{4}',{5},{6},{7},'{8}','{9}','{10}','{11}','{12}','{13}',{14},{15})"
With CharacterData
SQL = String.Format(SQL, CharacterTokenData.CharacterID,
FormatDBString(.name),
.corporation_id,
Format(CDate(.birthday.Replace("T", " ")), SQLiteDateFormat),
FormatDBString(.gender),
.race_id,
.bloodline_id,
FormatNullInteger(.ancestry_id),
FormatDBString(FormatNullString(.description)),
FormatDBString(CharacterTokenData.AccessToken),
Format(CharacterTokenData.TokenExpiration, SQLiteDateFormat),
FormatDBString(CharacterTokenData.TokenType),
FormatDBString(CharacterTokenData.RefreshToken),
FormatDBString(CharacterTokenData.Scopes),
0, 0) ' Don't set default yet or override skills
End With
End If
EVEDB.ExecuteNonQuerySQL(SQL)
rsCheck.Close()
' Update public cachedate for character now that we have a record
Call CB.UpdateCacheDate(CacheDateType.PublicCharacterData, CacheDate, CLng(CharacterTokenData.CharacterID))
' While we are here, load the public information of the corporation too
Call SetCorporationData(CharacterData.corporation_id, CacheDate)
' Update after we update/insert the record
Call CB.UpdateCacheDate(CacheDateType.PublicCorporationData, CacheDate, CharacterData.corporation_id)
If CharacterID = 0 And Not SupressMessages Then
MsgBox("Character successfully added to IPH", vbInformation, Application.ProductName)
End If
Return True
Else
' Just didn't need to update yet
Return True
End If
Catch ex As Exception
MsgBox("Unable to set character data data through ESI: " & ex.Message, vbInformation, Application.ProductName)
End Try
Return False
End Function
''' <summary>
''' Retrieves the public data about the character ID sent
''' </summary>
''' <param name="CharacterID">CharacterID you want public data for</param>
''' <returns>Returns data in the ESICharacterPublicData JSON property class</returns>
Public Function GetCharacterPublicData(ByVal CharacterID As Long, ByRef DataCacheDate As Date) As ESICharacterPublicData
Dim CharacterData As ESICharacterPublicData
Dim PublicData As String
PublicData = GetPublicData(ESIURL & "characters/" & CStr(CharacterID) & "/" & TranquilityDataSource, DataCacheDate)
If Not IsNothing(PublicData) Then
CharacterData = JsonConvert.DeserializeObject(Of ESICharacterPublicData)(PublicData)
Return CharacterData
Else
Return Nothing
End If
End Function
#End Region
#Region "Auth Processing"
Public Function GetCharacterSkills(ByVal CharacterID As Long, ByVal TokenData As SavedTokenData, ByRef SkillsCacheDate As Date) As EVESkillList
Dim SkillData As New ESICharacterSkillsBase
Dim ReturnData As String
Dim ReturnSkills As New EVESkillList(UserApplicationSettings.UseActiveSkillLevels)
Dim TempSkill As New EVESkill
ReturnData = GetPrivateAuthorizedData(ESIURL & "characters/" & CStr(CharacterID) & "/skills/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, SkillsCacheDate, CharacterID)
If Not IsNothing(ReturnData) Then
SkillData = JsonConvert.DeserializeObject(Of ESICharacterSkillsBase)(ReturnData)
For Each entry In SkillData.skills
TempSkill = New EVESkill
TempSkill.TypeID = entry.skill_id
TempSkill.TrainedLevel = entry.trained_skill_level
TempSkill.ActiveLevel = entry.active_skill_level
TempSkill.SkillPoints = entry.skillpoints_in_skill
Call ReturnSkills.InsertSkill(TempSkill, True)
Next
Return ReturnSkills
Else
Return Nothing
End If
End Function
Public Function GetCharacterStandings(ByVal CharacterID As Long, ByVal TokenData As SavedTokenData, ByRef StandingsCacheDate As Date) As EVENPCStandings
Dim TempStandingsList As New EVENPCStandings
Dim StandingsData As List(Of ESICharacterStandingsData)
Dim ReturnData As String = ""
Dim StandingType As String = ""
ReturnData = GetPrivateAuthorizedData(ESIURL & "characters/" & CStr(CharacterID) & "/standings/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, StandingsCacheDate, CharacterID)
If Not IsNothing(ReturnData) Then
StandingsData = JsonConvert.DeserializeObject(Of List(Of ESICharacterStandingsData))(ReturnData)
For Each entry In StandingsData
Select Case entry.from_type
Case "agents"
StandingType = "Agent"
Case "faction"
StandingType = "Faction"
Case "npc_corp"
StandingType = "Corporation"
End Select
TempStandingsList.InsertStanding(entry.from_id, StandingType, "", entry.standing)
Next
Return TempStandingsList
Else
Return Nothing
End If
End Function
Public Function GetCurrentResearchAgents(ByVal CharacterID As Long, ByVal TokenData As SavedTokenData, ByRef AgentsCacheDate As Date) As List(Of ESIResearchAgent)
Dim ReturnData As String
ReturnData = GetPrivateAuthorizedData(ESIURL & "characters/" & CStr(CharacterID) & "/agents_research/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, AgentsCacheDate, CharacterID)
If Not IsNothing(ReturnData) Then
Return JsonConvert.DeserializeObject(Of List(Of ESIResearchAgent))(ReturnData)
Else
Return Nothing
End If
End Function
Public Function GetBlueprints(ByVal ID As Long, ByVal TokenData As SavedTokenData, ByVal ScanType As ScanType, ByRef BPCacheDate As Date) As List(Of EVEBlueprint)
Dim ReturnedBPs As New List(Of EVEBlueprint)
Dim TempBlueprint As EVEBlueprint
Dim RawBPData As New List(Of ESIBlueprint)
Dim ReturnData As String = ""
Dim rsLookup As SQLiteDataReader
' Set up query string
If ScanType = ScanType.Personal Then
ReturnData = GetPrivateAuthorizedData(ESIURL & "characters/" & CStr(ID) & "/blueprints/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, BPCacheDate, ID)
Else ' Corp
ReturnData = GetPrivateAuthorizedData(ESIURL & "corporations/" & CStr(ID) & "/blueprints/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, BPCacheDate, ID)
End If
If Not IsNothing(ReturnData) Then
RawBPData = JsonConvert.DeserializeObject(Of List(Of ESIBlueprint))(ReturnData)
' Process the return data
For Each BP In RawBPData
TempBlueprint.ItemID = BP.item_id
TempBlueprint.TypeID = BP.type_id
' Get the typeName for this bp
DBCommand = New SQLiteCommand("SELECT typeName FROM INVENTORY_TYPES WHERE typeID = " & CStr(BP.type_id), EVEDB.DBREf)
rsLookup = DBCommand.ExecuteReader
If rsLookup.Read Then
TempBlueprint.TypeName = rsLookup.GetString(0)
Else
TempBlueprint.TypeName = Unknown
End If
rsLookup.Close()
TempBlueprint.LocationID = BP.location_id
' Get the flag id for this location
DBCommand = New SQLiteCommand("SELECT flagID FROM INVENTORY_FLAGS WHERE flagName = '" & BP.location_flag & "'", EVEDB.DBREf)
rsLookup = DBCommand.ExecuteReader
If rsLookup.Read Then
TempBlueprint.FlagID = rsLookup.GetInt32(0)
Else
TempBlueprint.FlagID = 0
End If
rsLookup.Close()
TempBlueprint.Quantity = BP.quantity
TempBlueprint.MaterialEfficiency = BP.material_efficiency
TempBlueprint.TimeEfficiency = BP.time_efficiency
TempBlueprint.Runs = BP.runs
' We determine the type of bp from quantity
If TempBlueprint.Quantity = BPType.Original Or TempBlueprint.Quantity > 0 Then
' BPO or stack of BPOs
TempBlueprint.BPType = BPType.Original
ElseIf TempBlueprint.Quantity = BPType.Copy Then
' BPC
TempBlueprint.BPType = BPType.Copy
Else
' Not sure what this is
TempBlueprint.BPType = 0
End If
TempBlueprint.Owned = False
TempBlueprint.Scanned = True ' We just scanned it
TempBlueprint.Favorite = False
TempBlueprint.AdditionalCosts = 0
ReturnedBPs.Add(TempBlueprint)
Next
Return ReturnedBPs
Else
Return Nothing
End If
End Function
Public Function GetIndustryJobs(ByVal ID As Long, ByVal TokenData As SavedTokenData, ByVal JobType As ScanType, ByRef JobsCacheDate As Date) As List(Of ESIIndustryJob)
Dim ReturnData As String = ""
Dim URLType As String = ""
' Set up query string
If JobType = ScanType.Personal Then
URLType = "characters/"
Else ' Corp
URLType = "corporations/"
End If
ReturnData = GetPrivateAuthorizedData(ESIURL & URLType & CStr(ID) & "/industry/jobs/" & TranquilityDataSource & "&include_completed=true",
FormatTokenData(TokenData), TokenData.TokenExpiration, JobsCacheDate, ID)
If Not IsNothing(ReturnData) Then
Return JsonConvert.DeserializeObject(Of List(Of ESIIndustryJob))(ReturnData)
Else
Return Nothing
End If
End Function
Public Function GetAssets(ByVal ID As Long, ByVal TokenData As SavedTokenData, ByVal JobType As ScanType, ByRef AssetsCacheDate As Date) As List(Of ESIAsset)
Dim ReturnData As String = ""
' Set up query string
If JobType = ScanType.Personal Then
ReturnData = GetPrivateAuthorizedData(ESIURL & "characters/" & CStr(ID) & "/assets/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, AssetsCacheDate, ID)
Else ' Corp
ReturnData = GetPrivateAuthorizedData(ESIURL & "corporations/" & CStr(ID) & "/assets/" & TranquilityDataSource,
FormatTokenData(TokenData), TokenData.TokenExpiration, AssetsCacheDate, ID)
End If
If Not IsNothing(ReturnData) Then
Return JsonConvert.DeserializeObject(Of List(Of ESIAsset))(ReturnData)
Else
Return Nothing
End If
End Function
Public Structure AssetNamePairs
Dim AssetID As Double
Dim AssetName As String
End Structure
'Public Function GetAssetNames(ByVal ItemIDs As List(Of Double), ByVal ID As Long, ByVal TokenData As SavedTokenData,
' ByVal JobType As ScanType, ByRef AssetNamesCacheDate As Date) As List(Of ESICharacterAssetName)
' Dim Output As New List(Of ESICharacterAssetName)
' Dim TempOutput As New List(Of ESICharacterAssetName)
' Dim Success As Boolean = False
' Dim Data As String = ""
' Dim Token As ESITokenData = FormatTokenData(TokenData)
' Dim WC As New WebClient
' Dim Address As String = ""
' Dim PostParameters As New NameValueCollection