forked from jacob-keller/L0G-101086
-
Notifications
You must be signed in to change notification settings - Fork 0
/
l0g-101086.psm1
1753 lines (1470 loc) · 56.7 KB
/
l0g-101086.psm1
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
# SPDX-License-Identifier: BSD-3-Clause
# Copyright 2018 Jacob Keller. All rights reserved.
# This module contains several functions which are shared between the scripts
# related to uploading and formatting GW2 ArcDPS log files. It contains some
# general purpose utility functions, as well as functions related to managing
# the configuration file
<#
.Synopsis
Tests whether a path exists
.Description
Tests wither a given path exists. It is safe to pass a $null value to this
function, as it will return $false in that case.
.Parameter Path
The path to test
#>
Function X-Test-Path {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$path)
try {
return Test-Path $path.trim()
} catch {
return $false
}
}
<#
.Synopsis
Print output to the log file, or console.
.Description
Print output to the log file if it has been specified. Otherwise, output
will be displayed to the screen.
.Parameter string
The string to log
#>
Function Log-Output {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$string)
if ($script:logfile) {
Write-Output $string | Out-File -Append $script:logfile
} else {
Write-Output $string
}
}
<#
.Synopsis
Set the log file used by Log-Output
.Description
Set the log file used by the Log-Output function. If the log file has been
set, then Log-Output will log to the file. Otherwise it will log to the screen.
To clear the log file, set it to $null
.Parameter file
The file name to set for the log file (or $null to clear it)
#>
Function Set-Logfile {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$file)
$script:logfile = $file
}
<#
.Synopsis
Convert UTC time to the local timezone
.Description
Take a UTC date time object containing a UTC time and convert it to the
local time zone
.Parameter Time
The UTC time value to convert
#>
Function ConvertFrom-UTC {
[CmdletBinding()]
param([Parameter(Mandatory)][DateTime]$time)
[TimeZone]::CurrentTimeZone.ToLocalTime($time)
}
<#
.Synopsis
Convert a Unix timestamp to a DateTime object
.Description
Given a Unix timestamp (integer containing seconds since the Unix Epoch),
convert it to a DateTime object representing the same time.
.Parameter UnixDate
The Unix timestamp to convert
#>
Function ConvertFrom-UnixDate {
[CmdletBinding()]
param([Parameter(Mandatory)][int]$UnixDate)
ConvertFrom-UTC ([DateTime]'1/1/1970').AddSeconds($UnixDate)
}
<#
.Synopsis
Convert DateTime object into a Unix timestamp
.Description
Given a DateTime object, convert it to an integer representing seconds since
the Unix Epoch.
.Parameter Date
The DateTime object to convert
#>
Function ConvertTo-UnixDate {
[CmdletBinding()]
param([Parameter(Mandatory)][DateTime]$Date)
$UnixEpoch = [DateTime]'1/1/1970'
(New-TimeSpan -Start $UnixEpoch -End $Date).TotalSeconds
}
<#
.Synopsis
Returns the NoteProperties of a PSCustomObject
.Description
Given a PSCustomObject, return the names of each NoteProperty in the object
.Parameter obj
The PSCustomObject to match
#>
Function Keys {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$obj)
return @($obj | Get-Member -MemberType NoteProperty | % Name)
}
<#
.Description
Configuration fields which are valid for multiple versions of the
configuration file. Currently this is shared between the v1 and v2
formats, as they share a common base of configuration fields.
If path is set, then the configuration will allow exchanging %UserProfile%
for the current $env:USERPROFILE value
If validFields is set to an array if fields, then the subfield will be
recursively validated. If arrayFields is set, then the field will be treated as
an array of objects and each object in the array will be recursively validated.
Path, validFields, and arrayFields are mutually exclusive
#>
$commonConfigurationFields =
@(
@{
# Version indicating the format of the configuration file
name="config_version"
type=[int]
}
@{
# Setting debug_mode to true will modify some script behaviors
name="debug_mode"
type=[bool]
}
@{
# Setting experimental_arcdps will cause update-arcdps.ps1 to
# download the experimental version of ArcDPS instead of the
# stable version
name="experimental_arcdps"
type=[bool]
}
@{
# Path to the EVTC combat logs generated by ArcDPS
name="arcdps_logs"
type=[string]
path=$true
}
@{
# Path to a folder for storing the JSON we send to a discord webhook
# Intended for debugging if the logs do not format correctly. If
# this is set to a non-existent directory, then the discord webhooks
# will not be saved.
name="discord_json_data"
type=[string]
path=$true
}
@{
# Path to folder to store extra data about local EVTC encounter files
# Will contain files in the JSON format which have data exracted
# from the EVTC log file using simpleArcParse.
name="extra_upload_data"
type=[string]
path=$true
}
@{
# Deprecated configuration for path to store a "database" mapping encounter start
# times to the local EVTC extra upload data. This is no longer used as the
# functionality has been replaced with a better process.
name="gw2raidar_start_map"
type=[string]
path=$true
}
@{
# Path to a file which stores the last time that we formatted logs to discord
# Used to ensure that we don't re-post old logs. Disabled if debug_mode is true
name="last_format_file"
type=[string]
path=$true
}
@{
# Path to file to store the last time that we uploaded logs to gw2raidar and dps.report
# This is *not* disabled when debug_mode is true, because we don't want to spam
# the uploads of old encounters.
name="last_upload_file"
type=[string]
path=$true
}
@{
# Path to the compiled binary for the simpleArcParse program
name="simple_arc_parse_path"
type=[string]
path=$true
}
@{
# Path to a file which logs actions and data generated while uploading logs
name="upload_log_file"
type=[string]
path=$true
}
@{
# Path to a file which logs actions and data generated while formatting to discord
name="format_encounters_log"
type=[string]
path=$true
}
@{
# Path to the GW2 installation directory
name="guildwars2_path"
type=[string]
path=$true
}
@{
# Path to Launch Buddy program (used by launcher.ps1)
name="launchbuddy_path"
type=[string]
path=$true
}
@{
# Path to a folder which holds backups of DLLs for arcdps, and related plugins
name="dll_backup_path"
type=[string]
path=$true
}
@{
# Path to the RestSharp DLL used for contacting gw2raidar and dps.report
name="restsharp_path"
type=[string]
path=$true
}
@{
# The gw2raidar API token used with your account. Used to upload encounters to
# gw2raidar, as well as look up previously uploaded encounter data.
name="gw2raidar_token"
type=[string]
}
@{
# An API token used by dps.report. Not currently required by dps.report but
# may be used in a future API update to allow searching for previously uploaded
# logs.
name="dps_report_token"
type=[string]
}
@{
# dps.report allows using alternative generators besides raid heros. This parameter
# is used to configure the generator used by the site, and must match a valid value
# from their API. Currently "rh" means RaidHeros, "ei" means EliteInsights, and
# leaving it blank will use the current default generator.
name="dps_report_generator"
type=[string]
}
@{
# If set, configures whether and how to upload to dps.report
# "no" disables uploading to dps.report entirely
# "successful" causes only successful encounters to be uploaded
# "all" causes all encounters to be uploaded.
# The default is "successful"
name="upload_dps_report"
type=[string]
validStrings=@("no", "successful", "all")
}
@{
# If set, configures whether and how to upload to gw2raidar
# "no" disables uploading to gw2raidar entirely
# "successful" causes only successful encounters to be uploaded
# "all" causes all encounters to be uploaded.
# The default is "successful"
name="upload_gw2raidar"
type=[string]
validStrings=@("no", "successful", "all")
}
)
<#
.Description
Configuration fields which are valid for a v1 configuration file. Anything
not listed here will be excluded from the generated $config object. If one
of the fields has an incorrect type, configuration will fail to be validated.
Fields which are common to many versions of the configuration file are stored
in $commonConfigurationFields
#>
$v1ConfigurationFields = $commonConfigurationFields +
@(
@{
name="custom_tags_script"
type=[string]
path=$true
}
@{
name="discord_webhook"
type=[string]
}
@{
name="guild_thumbnail"
type=[string]
}
@{
name="gw2raidar_tag_glob"
type=[string]
}
@{
name="guild_text"
type=[string]
}
@{
name="discord_map"
type=[PSCustomObject]
}
@{
name="emoji_map"
type=[PSCustomObject]
}
@{
name="publish_fractals"
type=[bool]
}
)
<#
.Description
Configuration fields which are valid for a v2 configuration file. Anything
not listed here will be excluded from the generated $config object. If one
of the fields has an incorrect type, configuration will fail to be validated.
Fields which are common to many versions of the configuration file are stored
in $commonConfigurationFields
#>
$v2ValidGuildFields =
@(
@{
# The name of this guild
name="name"
type=[string]
}
@{
# Priority for determining which guild ran an encounter if there are
# conflicts. Lower numbers win ties.
name="priority"
type=[int]
}
@{
# Tag to add when uploading to gw2raidar.
name="gw2raidar_tag"
type=[string]
}
@{
# Category to use when uploading to gw2raidar.
# 1: Guild/ Static
# 2: Training
# 3: PUG
# 4: Low Man / Sells
name="gw2raidar_category"
type=[int]
}
@{
# Minimum number of players required for an encounter to be considered
# a guild run. 0 indicates any encounter can be considered if there is
# no better guild available
name="threshold"
type=[int]
}
@{
# The discord webhook URL for this guild
name="webhook_url"
type=[string]
}
@{
# URL to a thumbnail image for this guild
name="thumbnail"
type=[string]
}
@{
# Set this to true if this guild should be considered for fractal
# challenge motes. If set to false, fractals will never be posted
# to this guild.
name="fractals"
type=[bool]
}
@{
# Set of gw2 account names associated with this guild, mapped to
# their discord account ids. Used as the primary mechanism to determine
# which guild the encounter was run by, as well as for posting player pings
# to the discord webhook.
name="discord_map"
type=[PSCustomObject]
}
@{
# emoji IDs used to provide pictures for each boss. Due to limitations of
# the webhook API, we can't use normal image URLs, but only emojis
# Each boss can have one emoji associated. If the map is empty for that boss
# then only the boss name will appear, without any emoji icon.
name="emoji_map"
type=[PSCustomObject]
}
@{
# If set to true, format-encounters will publish every post to this guilds
# discord. If unset or if set to false, only the encounters which match
# this guild will be published to the guild's discord.
name="everything"
type=[bool]
optional=$true
}
)
$v2ConfigurationFields = $commonConfigurationFields +
@(
@{
name="guilds"
type=[Object[]]
arrayFields=$v2ValidGuildFields
}
)
<#
.Description
An enumeration defining methods for converting path-like fields
This enumeration defines the methods of converting path-like strings, which
support reading %UserProfile% as the $env.UserProfile environment variable.
FromUserProfile will allow converting the %UserProfile% string to the
UserProfile environment variable when reading the config in from disk.
ToUserProfile will allow converting the value of the UserProfile environment
variable into %UserProfile% when writing back out to disk.
#>
Add-Type -TypeDefinition @"
public enum PathConversion
{
FromUserProfile,
ToUserProfile,
}
"@
<#
.Synopsis
Validate fields of an object
.Description
Given a set of field definitions, validate that the given object has fields
of the correct type, possibly recursively.
Return the object on success, with updated path data if necessary. Unknown fields
will be removed from the returned object.
Return $null if the object has invalid fields or is missing required fields.
.Parameter object
The object to validate
.Parameter fields
The field definition
.Parameter RequiredFields
Specifies which fiels are required to exist. If a required field is missing, an error is
generated.
.Parameter conversion using the PathConversion enum
Optional parameter specifying how to convert path-like configuration values. The
default mode is to convert from %UserProfile% to the environment value for UserProfile
#>
Function Validate-Object-Fields {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$Object,
[Parameter(Mandatory)][array]$Fields,
[Parameter(Mandatory)][AllowEmptyCollection()][array]$RequiredFields,
[PathConversion]$conversion = [PathConversion]::FromUserProfile)
# Make sure all the required parameters are actually valid
ForEach ($parameter in $RequiredFields) {
if ($parameter -notin ($Fields | ForEach-Object { $_.name })) {
Read-Host -Prompt "BUG: $parameter is not a valid parameter. Press enter to exit"
exit
}
}
# Select only the known properties, ignoring unknown properties
$Object = $Object | Select-Object -Property ($Fields | ForEach-Object { $_.name } | where { $Object."$_" -ne $null })
$invalid = $false
foreach ($field in $Fields) {
# Make sure required parameters are available
if (-not (Get-Member -InputObject $Object -Name $field.name)) {
if ($field.name -in $RequiredFields) {
Write-Host "$($field.name) is a required parameter for this script."
$invalid = $true
}
continue
}
# Make sure that the field has the expected type
if ($Object."$($field.name)" -isnot $field.type) {
Write-Host "$($field.name) has an unexpected type [$($Object."$($field.name)".GetType().name)]"
$invalid = $true
continue;
}
if ($field.path) {
# Handle %UserProfile% in path fields
switch ($conversion) {
"FromUserProfile" {
$Object."$($field.name)" = $Object."$($field.name)".replace("%UserProfile%", $env:USERPROFILE)
}
"ToUserProfile" {
$Object."$($field.name)" = $Object."$($field.name)".replace($env:USERPROFILE, "%UserProfile%")
}
}
} elseif ($field.validFields) {
# Recursively validate subfields. All fields not explicitly marked "optional" must be present
$Object."$($field.name)" = Validate-Object-Fields $Object."$($field.name)" $field.validFields ($field.validFields | where { -not ( $_.optional -eq $true ) } | ForEach-Object { $_.name } )
} elseif ($field.arrayFields) {
# Recursively validate subfields of an array of objects. All fields not explicitly marked "optional" must be present
$ValidatedSubObjects = @()
$arrayObjectInvalid = $false
ForEach ($SubObject in $Object."$($field.name)") {
$SubObject = Validate-Object-Fields $SubObject $field.arrayFields ($field.arrayFields | where { -not ( $_.optional -eq $true ) } | ForEach-Object { $_.name } )
if (-not $SubObject) {
$arrayObjectInvalid = $true
break;
}
$ValidatedSubObjects += $SubObject
}
# If any of the sub fields was invalid, the whole array is invalid
if ($arrayObjectInvalid) {
$Object."$($field.name)" = $null
} else {
$Object."$($field.name)" = $ValidatedSubObjects
}
} elseif ($field.validStrings) {
if (-not $field.validStrings.Contains($Object."$($field.name)")) {
$fieldname = $field.name
$value = $Object."$fieldname"
Write-Host "$value is not a valid value for $fieldname"
$invalid = $true
}
}
# If the subfield is now null, then the recursive validation failed, and this whole field is invalid
if ($Object."$($field.name)" -eq $null) {
$invalid = $true
}
}
if ($invalid) {
Read-Host -Prompt "Configuration file has invalid parameters. Press enter to exit"
return
}
return $Object
}
<#
.Synopsis
Validate a configuration object to make sure it has correct fields
.Description
Take a $config object, and verify that it has valid parameters with the expected
information and types. Return the $config object on success (with updated path names)
Return $null if the $config object is not valid.
.Parameter config
The configuration object to validate
.Parameter version
The expected configuration version, used to ensure that the config object matches
the configuration version used by the script requesting it.
.Parameter RequiredParameters
The parameters that are required by the invoking script
.Parameter conversion using the PathConversion enum
Optional parameter specifying how to convert path-like configuration values. The
default mode is to convert from %UserProfile% to the environment value for UserProfile
#>
Function Validate-Configuration {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$config,
[Parameter(Mandatory)][int]$version,
[Parameter(Mandatory)][AllowEmptyCollection()][array]$RequiredParameters,
[PathConversion]$conversion = [PathConversion]::FromUserProfile)
if ($version -eq 1) {
$configurationFields = $v1ConfigurationFields
} elseif ($version -eq 2) {
$configurationFields = $v2ConfigurationFields
} else {
Read-Host -Prompt "BUG: configuration validation does not support version ${version}. Press enter to exit"
exit
}
# Make sure the config_version is set to 1. This should only be bumped if
# the expected configuration names change. New fields should not cause a
# bump in this version, but only removal or change of fields.
#
# Scripts should be resilient against new parameters not being configured.
if ($config.config_version -ne $version) {
Read-Host -Prompt "This script only knows how to understand config_version=${version}. Press enter to exit"
return
}
$config = Validate-Object-Fields $config $configurationFields $RequiredParameters $conversion
return $config
}
<#
.Synopsis
Load the configuration file and return a configuration object
.Description
Load the specified configuration file and return a valid configuration
object. Will ignore unknown fields in the configuration JSON, and will
convert magic path strings in path-like fields
.Parameter ConfigFile
The configuration file to load
.Parameter version
The version of the config file we expect, defaults to 1 currently.
.Parameter RequiredParameters
An array of parameters required by the script. Will ensure that the generated
config object has non-null values for the specified paramters. Defaults to
an empty array, meaning no parameters are required.
#>
Function Load-Configuration {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$ConfigFile,
[int]$version = 2,
[AllowEmptyCollection()][array]$RequiredParameters = @())
# Check that the configuration file path is valid
if (-not (X-Test-Path $ConfigFile)) {
Read-Host -Prompt "Unable to locate the configuration file. Press enter to exit"
return
}
# Parse the configuration file and convert it from JSON
try {
$config = Get-Content -Raw -Path $ConfigFile | ConvertFrom-Json
} catch {
Write-Error ($_.Exception | Format-List -Force | Out-String) -ErrorAction Continue
Write-Error ($_.InvocationInfo | Format-List -Force | Out-String) -ErrorAction Continue
Write-Host "Unable to read the configuration file"
Read-Host -Prompt "Press enter to exit"
return
}
$config = (Validate-Configuration $config $version $RequiredParameters FromUserProfile)
if (-not $config) {
return
}
return $config
}
<#
.Synopsis
Return true if this is a fractal id, false otherise
.Parameter id
The ArcDPS EVTC encounter id
#>
Function Is-Fractal-Encounter {
[CmdletBinding()]
param([Parameter(Mandatory)][int]$id)
# 99CM and 100CM encounter IDs
$FractalIds = @(0x427d, 0x4284, 0x4234, 0x44e0, 0x461d, 0x455f)
return [bool]($id -in $FractalIds)
}
<#
.Synopsis
Determine which guild "ran" this encounter.
.Description
Given a list of players and an encounter id, determine which guild ran this
encounter. We determine which guild the encounter belongs to by picking
the guild who has the most players involved. If there is a tie, we break it
by the priority.
If the encounter is a fractal, then only guilds who have fractals set to
true will be considered. Thus, even if one guild has more members in the
encounter, but does not have have fractals set to true, the encounter
may be associated with the smaller guild in this case.
.Parameter guilds
The array of guilds to consider
.Parameter players
An array of players who were involved in this encounter
.Parameter id
The encounter id, used to determine whether this was a fractal
#>
Function Determine-Guild {
[CmdletBinding()]
param([Parameter(Mandatory)][Object[]]$Guilds,
[Parameter(Mandatory)][array]$Players,
[Parameter(Mandatory)][int]$id)
# First remove any non-fractal guilds
if (Is-Fractal-Encounter $id) {
$AvailableGuilds = $Guilds | where { $_.fractals }
} else {
$AvailableGuilds = $Guilds
}
$GuildData = $AvailableGuilds | ForEach-Object {
$guild = $_
$activeMembers = @($players | where {(Keys $guild.discord_map) -Contains $_}).Length
# Only consider this guild if it meets the player threshold
if ($activeMembers -lt $guild.threshold) {
return
}
# Return a data object indicating the name, priority, and number of
# active members in this encounter
return [PSCustomObject]@{
name = $guild.name
priority = $guild.priority
activeMembers = $activeMembers
}
}
# No suitable guild was found
if ($GuildData.Length -eq 0) {
return
}
# Return the name of the most eligible guild
return @($GuildData | Sort-Object @{Expression="activeMembers";Descending=$true},priority)[0].name
}
<#
.Synopsis
Print out details about an exception that occurred.
.Description
Write out details about an exception that was caught.
.Parameter e
The exception object to dump.
#>
Function Write-Exception {
[CmdletBinding()]
param([Parameter(Mandatory)][Object]$e)
# Combine the exception and invocation parameters together into a single list
$info = $e.InvocationInfo | Select *
$e.Exception | Get-Member -MemberType Property | ForEach-Object {
$info | Add-Member -MemberType NoteProperty -Name $_.Name -Value ( $e.Exception | Select-Object -ExpandProperty $_.Name )
}
Write-Error ( $info | Format-List -Force | Out-String) -ErrorAction Continue
}
<#
.Synopsis
Write configuration object back out to a file
.Description
Writes the given configuration object back out to a file. It will also convert the profile
directory back to %UserProfile% so that the config is more easily re-usable.
.Parameter config
The config object to print out
.Parameter file
The path to the configuration file
#>
Function Write-Configuration {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$Config,
[Parameter(Mandatory)][string]$ConfigFile,
[Parameter(Mandatory)][string]$BackupFile)
if (X-Test-Path $ConfigFile) {
if (X-Test-Path $BackupFile) {
throw "The backup file must be deleted prior to writing out the configuration file"
}
Move-Item $ConfigFile $BackupFile
}
# Make sure any changes are valid. Convert the UserProfile path back to %UserProfile%.
$writeConfig = (Validate-Configuration $Config $Config.config_version @() ToUserProfile)
if (-not $writeConfig) {
throw "The configuration object is not valid."
}
# Write out the configuration to disk
$writeConfig | ConvertTo-Json -Depth 10 | Out-File -Force $ConfigFile
}
# ConvertTo-JSON doesn't handle unicode characters very well, but we want to
# insert a zero-width space. To do so, we'll implement a variant that replaces
# a magic string with the expected value
#
# More strings can be added here if necessary. The initial string should be
# something innocuous which won't be generated as part of any URL or other
# generated text, and is unlikely to appear on accident
<#
.Synopsis
Convert encounter payload to a JSON string, converting some magic strings
to unicode
.Description
ConvertTo-JSON doesn't handle some unicode characters very well, and by
default doesn't have a depth large enough to convert the encounter structures
into JSON.
To handle this, Convert-Payload will convert the given payload data for a webhook
into JSON using -Depth 10. Additionally it will convert some magic strings which
represent unicode characters, so that we can insert the unicode characters properly.
.Parameter payload
The payload custom object to convert.
#>
Function Convert-Payload {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$payload)
# Convert the object into a JSON string, using an increased
# depth so that the ConvertTo-Json will completely convert
# the layered object into JSON.
$json = ($payload | ConvertTo-Json -Depth 10)
# Map some custom strings to their unicode equivalents. If we need
# to use other unicode characters, they should be added here.
$unicode_map = @{"@UNICODE-ZWS@"="\u200b";
"@BOXDASH@"="\u2500";
"@EMDASH@"="\u2014";
"@MIDDLEDOT@"="\u00B7"}
# Because ConvertTo-Json doesn't really handle all of the
# unicode characters, we need to insert these after the fact by
# replacing the magic strings with unicode escape sequences.
$unicode_map.GetEnumerator() | ForEach-Object {
$json = $json.replace($_.key, $_.value)
}
return $json
}
<#
.Synopsis
Look up the guild object based on the name
.Description
Find the first matching guild object from configuration based on the
guild name. Returns $null if no guilds by that name exist in configuration
.Parameter config
The configuration object
.Parameter guild_name
The guild name to lookup
#>
Function Lookup-Guild {
[CmdletBinding()]
param([Parameter(Mandatory)][PSCustomObject]$config,
[Parameter(Mandatory)][string]$guild_name)
ForEach ($guild in $config.guilds) {
if ($guild.name -eq $guild_name) {
return $guild
}
}
return $null
}
<#
.Synopsis
Convert GuildWars 2 account names to discord users
.Description
Given an array of GuildWars 2 account names, convert any known
player names into their discord equivalent. Unknown players will
be marked with italics markdown syntax.
.Parameter guild
The guild object to use for checking discord mappings
.Parameter accounts
The accounts to convert
#>
Function Get-Discord-Players {
[CmdletBinding()]
param([Parameter(Mandatory)][object]$guild,
[Parameter(Mandatory)][array]$accounts)
$names = @()
ForEach ($account in ($accounts | Sort)) {
if ($guild.discord_map."$account") {
$names += @($guild.discord_map."$account")
} elseif ($account -ne "") {
$names += @("_${account}_")
}
}
return $names
}
<#
.Synopsis
Given a boss name, look up the associated raid wing or fractal CM
.Description
Convert the boss name into the equivalent wing. Additionally, convert the
fractal bosses into their respective CMs as well.
.Parameter boss_name
The boss name to lookup
#>
Function Convert-Boss-To-Wing {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$boss_name)
$wings = @{"Vale Guardian"=1;
"Gorseval"=1;
"Sabetha"=1;
"Slothasor"=2;
"Matthias"=2;
"Keep Construct"=3;
"Xera"=3;
"Cairn"=4;
"Mursaat Overseer"=4;
"Samarog"=4;
"Deimos"=4;
"Soulless Horror"=5;
"Dhuum"=5;
"Conjured Amalgamate"=6;
"Largos Twins"=6;
"Qadim"=6;
"MAMA (CM)"="99cm";
"Siax (CM)"="99cm";
"Ensolyss (CM)"="99cm";
"Skorvald (CM)"="100cm";
"Artsariiv (CM)"="100cm";
"Arkk (CM)"="100cm";}
try {
return $wings[$boss_name];
} catch {
return $null
}
}
<#
.Synopsis
Get the abbreviated name for a boss, if there is one
.Description
Some boss names are a bit too long when displayed in an embed, and result in
unwanted spacing of multiple bosses when viewed in the desktop view. To fix this
we abbreviate some of them to a shorter version that doesn't cause the embeds to
have awkward spacing.
.Parameter boss_name