mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-21 08:48:38 +00:00
Compare commits
370 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4c20df4a8 | ||
|
|
5135677360 | ||
|
|
f6004f558b | ||
|
|
ddf08c4dc0 | ||
|
|
388eaf614d | ||
|
|
3a0768f9ef | ||
|
|
cb0e04c022 | ||
|
|
55cdac205d | ||
|
|
1bc0d6f16c | ||
|
|
f7377a7043 | ||
|
|
76f1ad45dd | ||
|
|
f21ffde803 | ||
|
|
e9c96f175f | ||
|
|
0f9a4c7c31 | ||
|
|
87451615e8 | ||
|
|
fa19aaae2e | ||
|
|
6b8280fceb | ||
|
|
7a13895429 | ||
|
|
75668ea099 | ||
|
|
d3aa881f55 | ||
|
|
33a1fdf556 | ||
|
|
0c848ec366 | ||
|
|
21d4e46b81 | ||
|
|
7a21c7bc45 | ||
|
|
9358aa5d1f | ||
|
|
6c9e9da740 | ||
|
|
a78a607f73 | ||
|
|
879323ed20 | ||
|
|
4e081b26a1 | ||
|
|
6cd3459dd4 | ||
|
|
482576f16d | ||
|
|
f63dbffee3 | ||
|
|
d9cdc806fe | ||
|
|
36a78b55a4 | ||
|
|
ab983099cc | ||
|
|
aab8f5f0b5 | ||
|
|
6b0bf0f9c1 | ||
|
|
3968130e15 | ||
|
|
dd488a1c9c | ||
|
|
de3c332803 | ||
|
|
684b7b6e28 | ||
|
|
d36f16e205 | ||
|
|
c3ab3d767f | ||
|
|
a790c34976 | ||
|
|
be208eab92 | ||
|
|
c9598709d6 | ||
|
|
367e7f841c | ||
|
|
8c988cfed6 | ||
|
|
5b15535661 | ||
|
|
e0c692e0ab | ||
|
|
1ddf6b34e2 | ||
|
|
8476c8c221 | ||
|
|
24ec938565 | ||
|
|
0dbfba275e | ||
|
|
55c5c70b52 | ||
|
|
17aa8297da | ||
|
|
b0aa2a9104 | ||
|
|
898c402dfc | ||
|
|
9258819c84 | ||
|
|
b5db0b511c | ||
|
|
7c6804449c | ||
|
|
ff28e2bf8f | ||
|
|
3510cd1be0 | ||
|
|
5320e8d9cc | ||
|
|
6ab9e2958d | ||
|
|
e47f286bda | ||
|
|
84b1599ca6 | ||
|
|
c5396a8ec8 | ||
|
|
9a9f18184b | ||
|
|
10d97e16e3 | ||
|
|
5ece500396 | ||
|
|
1a311513ca | ||
|
|
6e0e4835d1 | ||
|
|
ae4a784c3a | ||
|
|
287be65e7f | ||
|
|
476653a6cc | ||
|
|
407496efd6 | ||
|
|
9995b807f9 | ||
|
|
a6f4692b75 | ||
|
|
024b7ff824 | ||
|
|
68e46096ad | ||
|
|
d9f5f60854 | ||
|
|
82ccd4ddce | ||
|
|
5f69b337a6 | ||
|
|
dbbb6802d4 | ||
|
|
459cb44ff4 | ||
|
|
c1ebc813d5 | ||
|
|
948c42055b | ||
|
|
06d049d882 | ||
|
|
04f5e91e92 | ||
|
|
02a479ba13 | ||
|
|
01b99d20f6 | ||
|
|
9c6cd7f692 | ||
|
|
7899829dc7 | ||
|
|
d0693d362a | ||
|
|
227066f355 | ||
|
|
348c43b259 | ||
|
|
a23b7e1594 | ||
|
|
1c7952f8dd | ||
|
|
f0ef4c6ba6 | ||
|
|
eb71b640c5 | ||
|
|
6b7a0ff1ce | ||
|
|
d08740b6d7 | ||
|
|
70e3649e60 | ||
|
|
24d79f9b20 | ||
|
|
43c2ae6746 | ||
|
|
4bd2a3ec7f | ||
|
|
a90b375a72 | ||
|
|
9f1e562a19 | ||
|
|
4edfcff2e0 | ||
|
|
d4b48ab235 | ||
|
|
f1e5c31110 | ||
|
|
32c4522b47 | ||
|
|
fc760e1a84 | ||
|
|
2a92bf4dec | ||
|
|
716b253a04 | ||
|
|
0384365315 | ||
|
|
45dc910e01 | ||
|
|
f7e57e7d39 | ||
|
|
ace4151bbc | ||
|
|
088c3a35ed | ||
|
|
608bece8dc | ||
|
|
119caebfa8 | ||
|
|
2e0771b8d9 | ||
|
|
a08080a2ce | ||
|
|
d4bcdfde3e | ||
|
|
249ebfb590 | ||
|
|
56fc50e5ad | ||
|
|
5d8db36753 | ||
|
|
54f493467e | ||
|
|
7ed39db953 | ||
|
|
cc28d7520e | ||
|
|
109b307d0f | ||
|
|
178ecc2e4a | ||
|
|
ddfa7220d8 | ||
|
|
812523baef | ||
|
|
c5fd423a78 | ||
|
|
0b6c308d2e | ||
|
|
d6ef5e5404 | ||
|
|
9c304b8965 | ||
|
|
05c5a7fc30 | ||
|
|
2a2d4f09f1 | ||
|
|
ce5cd7bc8f | ||
|
|
a072a7b7d6 | ||
|
|
8a52f4fbbb | ||
|
|
ba07405d9a | ||
|
|
6239457f01 | ||
|
|
584fe4bd37 | ||
|
|
1f4fa2ed90 | ||
|
|
d24731999a | ||
|
|
1a3e82a1b0 | ||
|
|
f45b10f2ff | ||
|
|
2097fea6a0 | ||
|
|
c4bdb39c6d | ||
|
|
2f2b411293 | ||
|
|
860b979afb | ||
|
|
401d3f65f5 | ||
|
|
051cf043f3 | ||
|
|
4ebb4cfd9e | ||
|
|
d58a2d2717 | ||
|
|
51b764a39f | ||
|
|
0f94a05a69 | ||
|
|
16fd3c067f | ||
|
|
e13881763c | ||
|
|
90241c6076 | ||
|
|
d020a97209 | ||
|
|
d9ceea448f | ||
|
|
ec01846653 | ||
|
|
7d3c0a9d13 | ||
|
|
de88e3072b | ||
|
|
5e2ad8eb19 | ||
|
|
127107a96c | ||
|
|
1587c6facb | ||
|
|
042fadca28 | ||
|
|
4a9e6f6cc6 | ||
|
|
b7ac24eb7b | ||
|
|
c78d26a701 | ||
|
|
53614661c1 | ||
|
|
ea8c300a1a | ||
|
|
bb91bf3918 | ||
|
|
b9f72c293d | ||
|
|
7a14a394c2 | ||
|
|
dbf7148fbe | ||
|
|
59d51d1b15 | ||
|
|
acff4602cd | ||
|
|
44f135eb14 | ||
|
|
f4945c024e | ||
|
|
a329e2a3da | ||
|
|
608edf2569 | ||
|
|
f203e02a45 | ||
|
|
bdcc00af1f | ||
|
|
21d004fb26 | ||
|
|
d4ae307676 | ||
|
|
0a9993e85a | ||
|
|
1f2269dcf2 | ||
|
|
bb73916af0 | ||
|
|
12c4b7e924 | ||
|
|
c6b78b118c | ||
|
|
aaa6b6674a | ||
|
|
be2e173404 | ||
|
|
bfb189d55b | ||
|
|
3d503ed5ee | ||
|
|
ab01733860 | ||
|
|
dd0949b58d | ||
|
|
d825f74489 | ||
|
|
7f1ecdd585 | ||
|
|
01b2e205be | ||
|
|
d398e84f25 | ||
|
|
ac427ed1ec | ||
|
|
0118ccb614 | ||
|
|
697e059b66 | ||
|
|
45539018f5 | ||
|
|
1ebf2b6272 | ||
|
|
800dae280a | ||
|
|
bccdf269f0 | ||
|
|
757072cb01 | ||
|
|
b601d31d5c | ||
|
|
86ae501ce5 | ||
|
|
c22e5c146f | ||
|
|
4a710b4ffe | ||
|
|
f6ad3747f4 | ||
|
|
ec205bb7a2 | ||
|
|
464edddacf | ||
|
|
e6c6bce8a7 | ||
|
|
6a413b4d29 | ||
|
|
4f30e2e3c7 | ||
|
|
2bef94e3b4 | ||
|
|
cf94c417d2 | ||
|
|
e82100308c | ||
|
|
ada67a0f97 | ||
|
|
1bd20f7144 | ||
|
|
9b295ad85e | ||
|
|
d1953215e8 | ||
|
|
e480aca8b2 | ||
|
|
2befe20f76 | ||
|
|
c16485ad0b | ||
|
|
f036e99450 | ||
|
|
2804a36920 | ||
|
|
30e62813c7 | ||
|
|
621ce390c2 | ||
|
|
8d40423d9d | ||
|
|
1cf8959b92 | ||
|
|
56aafe3374 | ||
|
|
5570bd2999 | ||
|
|
9bec394436 | ||
|
|
3ae1a7ccfd | ||
|
|
e0b1c4c16f | ||
|
|
eb5bc560a4 | ||
|
|
f9309b7c54 | ||
|
|
3c2a154b39 | ||
|
|
23d07eb43e | ||
|
|
20af0edd4d | ||
|
|
4b29daabd4 | ||
|
|
a60513e998 | ||
|
|
188b96b951 | ||
|
|
b8e9dca6d3 | ||
|
|
157537c6ec | ||
|
|
a363b92075 | ||
|
|
f993d3d365 | ||
|
|
3f4520edf3 | ||
|
|
6e7041d8c5 | ||
|
|
e6cf5971a6 | ||
|
|
845af42080 | ||
|
|
c74481590b | ||
|
|
0ac5447198 | ||
|
|
e0428f8a91 | ||
|
|
cc0d2cb1d4 | ||
|
|
a0769eaf9a | ||
|
|
dc35545043 | ||
|
|
890709429c | ||
|
|
6f6a561b9e | ||
|
|
91d314c861 | ||
|
|
8abae9d4be | ||
|
|
26a390760e | ||
|
|
87933a2c92 | ||
|
|
1f843bb5d6 | ||
|
|
e87e78a372 | ||
|
|
91c82302bb | ||
|
|
d5a41dce1d | ||
|
|
40ab1d848c | ||
|
|
cc3a0a4144 | ||
|
|
5448403f43 | ||
|
|
636dd139c2 | ||
|
|
e7ad69be26 | ||
|
|
2f56b6dc3a | ||
|
|
ba7073df98 | ||
|
|
457bf6dfbb | ||
|
|
1e6279e1ca | ||
|
|
6e455d0eba | ||
|
|
9c9a74b448 | ||
|
|
b7790961fc | ||
|
|
5ecc050b12 | ||
|
|
c9819bde7f | ||
|
|
12660449ed | ||
|
|
7237e0affc | ||
|
|
85bb68825b | ||
|
|
48b8a28c7a | ||
|
|
b16a459ab8 | ||
|
|
da2fd37aa1 | ||
|
|
8d1d508fe5 | ||
|
|
92858de9e2 | ||
|
|
a7b1e01161 | ||
|
|
e14d00b760 | ||
|
|
d737201ab5 | ||
|
|
5fcd5d51f9 | ||
|
|
fa74d98879 | ||
|
|
27f965d4af | ||
|
|
ddd34d4a45 | ||
|
|
a3aa93fce8 | ||
|
|
0095d458e7 | ||
|
|
def3e26c92 | ||
|
|
10eb226722 | ||
|
|
3738ebed21 | ||
|
|
eff60bf307 | ||
|
|
b940af6e83 | ||
|
|
42ceb6d413 | ||
|
|
eef66cebf3 | ||
|
|
fac8cb2c9a | ||
|
|
9ce195b4ec | ||
|
|
c4bcb679f9 | ||
|
|
0d4871ca02 | ||
|
|
8397a69130 | ||
|
|
8515d72048 | ||
|
|
32af0abb6c | ||
|
|
e05f79b951 | ||
|
|
9699686da5 | ||
|
|
2052020d3d | ||
|
|
a894b7096a | ||
|
|
52b9d2d662 | ||
|
|
f3cc0b938a | ||
|
|
80c362d5ed | ||
|
|
c7546194f8 | ||
|
|
53993bfd34 | ||
|
|
181bc28462 | ||
|
|
aea9dee4ea | ||
|
|
a3270e4081 | ||
|
|
ccf191f1ba | ||
|
|
a76b6fc32f | ||
|
|
36ae066c65 | ||
|
|
8cf2d1bc94 | ||
|
|
d4e8182ffc | ||
|
|
96239a97f3 | ||
|
|
70d3ca47d7 | ||
|
|
a59087b574 | ||
|
|
306fbef3c2 | ||
|
|
2b7fc937ad | ||
|
|
3c338e05df | ||
|
|
dcf38c1357 | ||
|
|
500499ec19 | ||
|
|
d84c15b26c | ||
|
|
9597f21cd7 | ||
|
|
fedf477241 | ||
|
|
2291321173 | ||
|
|
2529ee301e | ||
|
|
ba1313f44f | ||
|
|
d640671640 | ||
|
|
f0a54875ed | ||
|
|
acca0a7a52 | ||
|
|
fa26f31d95 | ||
|
|
2851cffbc5 | ||
|
|
33649f95a8 | ||
|
|
d3142d077c | ||
|
|
dda62f6db6 | ||
|
|
578cb95b43 | ||
|
|
81118633e0 | ||
|
|
5ab84286dc | ||
|
|
b5e33c041a | ||
|
|
f4f73bcadc | ||
|
|
4f459e21f2 | ||
|
|
69352db431 |
@@ -36,7 +36,7 @@ csharp_prefer_simple_default_expression = true:warning
|
|||||||
csharp_prefer_simple_using_statement = true:warning
|
csharp_prefer_simple_using_statement = true:warning
|
||||||
csharp_prefer_static_local_function = true:warning
|
csharp_prefer_static_local_function = true:warning
|
||||||
|
|
||||||
csharp_preferred_modifier_order = public, protected, internal, private, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:warning
|
csharp_preferred_modifier_order = public, protected, internal, private, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async:warning
|
||||||
|
|
||||||
csharp_preserve_single_line_blocks = true
|
csharp_preserve_single_line_blocks = true
|
||||||
csharp_preserve_single_line_statements = false
|
csharp_preserve_single_line_statements = false
|
||||||
@@ -61,7 +61,6 @@ csharp_space_between_method_call_parameter_list_parentheses = false
|
|||||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
csharp_space_between_parentheses = none
|
|
||||||
csharp_space_between_square_brackets = false
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
csharp_style_conditional_delegate_call = true:warning
|
csharp_style_conditional_delegate_call = true:warning
|
||||||
@@ -79,15 +78,27 @@ csharp_style_expression_bodied_properties = true:warning
|
|||||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||||
csharp_style_inlined_variable_declaration = true:warning
|
csharp_style_inlined_variable_declaration = true:warning
|
||||||
|
|
||||||
csharp_style_pattern_local_over_anonymous_function = true:warning
|
csharp_style_namespace_declarations = file_scoped:warning
|
||||||
|
|
||||||
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||||
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
|
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
|
||||||
|
|
||||||
|
csharp_style_prefer_extended_property_pattern = true:warning
|
||||||
|
dotnet_style_prefer_foreach_explicit_cast_in_source = always:warning
|
||||||
csharp_style_prefer_index_operator = true:warning
|
csharp_style_prefer_index_operator = true:warning
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:warning
|
||||||
|
csharp_style_prefer_method_group_conversion = true:warning
|
||||||
csharp_style_prefer_not_pattern = true:warning
|
csharp_style_prefer_not_pattern = true:warning
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:warning
|
||||||
csharp_style_prefer_pattern_matching = true:warning
|
csharp_style_prefer_pattern_matching = true:warning
|
||||||
|
csharp_style_prefer_primary_constructors = true:warning
|
||||||
csharp_style_prefer_range_operator = true:warning
|
csharp_style_prefer_range_operator = true:warning
|
||||||
|
csharp_style_prefer_readonly_struct = true:warning
|
||||||
|
csharp_style_prefer_readonly_struct_member = true:warning
|
||||||
csharp_style_prefer_switch_expression = true:warning
|
csharp_style_prefer_switch_expression = true:warning
|
||||||
|
csharp_style_prefer_top_level_statements = false:warning
|
||||||
|
csharp_style_prefer_tuple_swap = true:warning
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:warning
|
||||||
|
|
||||||
csharp_style_throw_expression = true:warning
|
csharp_style_throw_expression = true:warning
|
||||||
|
|
||||||
@@ -98,13 +109,12 @@ csharp_style_var_elsewhere = false:warning
|
|||||||
csharp_style_var_for_built_in_types = false:warning
|
csharp_style_var_for_built_in_types = false:warning
|
||||||
csharp_style_var_when_type_is_apparent = false:warning
|
csharp_style_var_when_type_is_apparent = false:warning
|
||||||
|
|
||||||
csharp_using_directive_placement = outside_namespace
|
csharp_using_directive_placement = outside_namespace:warning
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# .NET Coding Conventions #
|
# .NET Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
|
||||||
[*.{cs,vb}]
|
|
||||||
dotnet_analyzer_diagnostic.severity = warning
|
dotnet_analyzer_diagnostic.severity = warning
|
||||||
|
|
||||||
dotnet_code_quality.ca3003.excluded_symbol_names = BotController
|
dotnet_code_quality.ca3003.excluded_symbol_names = BotController
|
||||||
@@ -114,6 +124,7 @@ dotnet_code_quality_unused_parameters = all:warning
|
|||||||
|
|
||||||
dotnet_diagnostic.ca1028.severity = silent
|
dotnet_diagnostic.ca1028.severity = silent
|
||||||
dotnet_diagnostic.ca1031.severity = silent
|
dotnet_diagnostic.ca1031.severity = silent
|
||||||
|
dotnet_diagnostic.ca1863.severity = silent
|
||||||
|
|
||||||
# Rule - almost everything
|
# Rule - almost everything
|
||||||
dotnet_naming_rule.almost_everything_must_be_pascal_case.severity = warning
|
dotnet_naming_rule.almost_everything_must_be_pascal_case.severity = warning
|
||||||
@@ -185,6 +196,7 @@ dotnet_sort_system_directives_first = true
|
|||||||
dotnet_style_coalesce_expression = true:warning
|
dotnet_style_coalesce_expression = true:warning
|
||||||
dotnet_style_collection_initializer = true:warning
|
dotnet_style_collection_initializer = true:warning
|
||||||
dotnet_style_explicit_tuple_names = true:warning
|
dotnet_style_explicit_tuple_names = true:warning
|
||||||
|
dotnet_style_namespace_match_folder = true:warning
|
||||||
dotnet_style_null_propagation = true:warning
|
dotnet_style_null_propagation = true:warning
|
||||||
dotnet_style_object_initializer = true:warning
|
dotnet_style_object_initializer = true:warning
|
||||||
|
|
||||||
@@ -218,7 +230,7 @@ dotnet_style_require_accessibility_modifiers = always:warning
|
|||||||
# JetBrains, IntelliJ/Rider #
|
# JetBrains, IntelliJ/Rider #
|
||||||
###############################
|
###############################
|
||||||
|
|
||||||
[*.{csproj,props,xml}]
|
[*.{csproj,props,resx,xml}]
|
||||||
ij_xml_keep_blank_lines = 1
|
ij_xml_keep_blank_lines = 1
|
||||||
ij_xml_keep_line_breaks = false
|
ij_xml_keep_line_breaks = false
|
||||||
ij_xml_keep_line_breaks_in_text = false
|
ij_xml_keep_line_breaks_in_text = false
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
@@ -12,6 +12,8 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is a bug report
|
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is a bug report
|
||||||
required: true
|
required: true
|
||||||
|
- label: This is not a **[duplicate](https://github.com/JustArchiNET/ArchiSteamFarm/issues?q=is%3Aissue)** of an existing issue
|
||||||
|
required: true
|
||||||
- label: I don't own more than **[10 accounts in total](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ#how-many-bots-can-i-run-with-asf)**
|
- label: I don't own more than **[10 accounts in total](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ#how-many-bots-can-i-run-with-asf)**
|
||||||
required: true
|
required: true
|
||||||
- label: I'm not using **[custom plugins](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Plugins)**
|
- label: I'm not using **[custom plugins](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Plugins)**
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/Enhancement-idea.yml
vendored
2
.github/ISSUE_TEMPLATE/Enhancement-idea.yml
vendored
@@ -12,6 +12,8 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is an enhancement idea
|
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is an enhancement idea
|
||||||
required: true
|
required: true
|
||||||
|
- label: This is not a **[duplicate](https://github.com/JustArchiNET/ArchiSteamFarm/issues?q=is%3Aissue)** of an existing issue
|
||||||
|
required: true
|
||||||
- label: My idea doesn't duplicate existing ASF functionality described on the **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)**
|
- label: My idea doesn't duplicate existing ASF functionality described on the **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)**
|
||||||
required: true
|
required: true
|
||||||
- label: I believe that my idea falls into ASF's scope and should be offered as part of ASF built-in functionality
|
- label: I believe that my idea falls into ASF's scope and should be offered as part of ASF built-in functionality
|
||||||
|
|||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -7,6 +7,8 @@ env:
|
|||||||
DOTNET_NOLOGO: true
|
DOTNET_NOLOGO: true
|
||||||
DOTNET_SDK_VERSION: 8.0
|
DOTNET_SDK_VERSION: 8.0
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -25,7 +27,7 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v4.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload latest strings for translation on Crowdin
|
- name: Upload latest strings for translation on Crowdin
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
||||||
uses: crowdin/github-action@v1.15.2
|
uses: crowdin/github-action@v1.19.0
|
||||||
with:
|
with:
|
||||||
crowdin_branch_name: main
|
crowdin_branch_name: main
|
||||||
config: '.github/crowdin.yml'
|
config: '.github/crowdin.yml'
|
||||||
|
|||||||
16
.github/workflows/code-quality.yml
vendored
16
.github/workflows/code-quality.yml
vendored
@@ -6,17 +6,17 @@ env:
|
|||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
DOTNET_NOLOGO: true
|
DOTNET_NOLOGO: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
security-events: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
@@ -33,13 +33,13 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Run Qodana scan
|
- name: Run Qodana scan
|
||||||
uses: JetBrains/qodana-action@v2023.2.8
|
uses: JetBrains/qodana-action@v2023.3.1
|
||||||
with:
|
with:
|
||||||
args: --property=idea.headless.enable.statistics=false
|
args: --property=idea.headless.enable.statistics=false
|
||||||
env:
|
env:
|
||||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||||
|
|
||||||
- name: Report Qodana results to GitHub
|
- name: Report Qodana results to GitHub
|
||||||
uses: github/codeql-action/upload-sarif@v2.22.7
|
uses: github/codeql-action/upload-sarif@v3.24.5
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||||
|
|||||||
2
.github/workflows/docker-ci.yml
vendored
2
.github/workflows/docker-ci.yml
vendored
@@ -5,6 +5,8 @@ on: [push, pull_request]
|
|||||||
env:
|
env:
|
||||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
3
.github/workflows/docker-publish-latest.yml
vendored
3
.github/workflows/docker-publish-latest.yml
vendored
@@ -9,6 +9,9 @@ env:
|
|||||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||||
TAG: latest
|
TAG: latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
5
.github/workflows/docker-publish-main.yml
vendored
5
.github/workflows/docker-publish-main.yml
vendored
@@ -10,6 +10,9 @@ env:
|
|||||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||||
TAG: main
|
TAG: main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -71,7 +74,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
|
|
||||||
- name: Update DockerHub repository description
|
- name: Update DockerHub repository description
|
||||||
uses: peter-evans/dockerhub-description@v3.4.2
|
uses: peter-evans/dockerhub-description@v4.0.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ env:
|
|||||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||||
TAG: released
|
TAG: released
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
2
.github/workflows/lock-threads.yml
vendored
2
.github/workflows/lock-threads.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Lock inactive threads
|
- name: Lock inactive threads
|
||||||
uses: dessant/lock-threads@v5.0.0
|
uses: dessant/lock-threads@v5.0.1
|
||||||
with:
|
with:
|
||||||
discussion-inactive-days: 90
|
discussion-inactive-days: 90
|
||||||
issue-inactive-days: 60
|
issue-inactive-days: 60
|
||||||
|
|||||||
54
.github/workflows/publish.yml
vendored
54
.github/workflows/publish.yml
vendored
@@ -4,10 +4,14 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CONFIGURATION: Release
|
CONFIGURATION: Release
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
|
DOTNET_NOLOGO: true
|
||||||
DOTNET_SDK_VERSION: 8.0
|
DOTNET_SDK_VERSION: 8.0
|
||||||
NODE_JS_VERSION: 'lts/*'
|
NODE_JS_VERSION: 'lts/*'
|
||||||
PLUGINS: ArchiSteamFarm.OfficialPlugins.ItemsMatcher ArchiSteamFarm.OfficialPlugins.MobileAuthenticator ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
PLUGINS: ArchiSteamFarm.OfficialPlugins.ItemsMatcher ArchiSteamFarm.OfficialPlugins.MobileAuthenticator ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-asf-ui:
|
publish-asf-ui:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -20,7 +24,7 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup Node.js with npm
|
- name: Setup Node.js with npm
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
check-latest: true
|
check-latest: true
|
||||||
node-version: ${{ env.NODE_JS_VERSION }}
|
node-version: ${{ env.NODE_JS_VERSION }}
|
||||||
@@ -38,7 +42,7 @@ jobs:
|
|||||||
run: npm run-script deploy --no-progress --prefix ASF-ui
|
run: npm run-script deploy --no-progress --prefix ASF-ui
|
||||||
|
|
||||||
- name: Upload ASF-ui
|
- name: Upload ASF-ui
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: ASF-ui
|
name: ASF-ui
|
||||||
path: ASF-ui/dist
|
path: ASF-ui/dist
|
||||||
@@ -69,10 +73,6 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
env:
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
|
||||||
DOTNET_NOLOGO: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v4.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
run: dotnet --info
|
run: dotnet --info
|
||||||
|
|
||||||
- name: Download previously built ASF-ui
|
- name: Download previously built ASF-ui
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: ASF-ui
|
name: ASF-ui
|
||||||
path: ASF-ui/dist
|
path: ASF-ui/dist
|
||||||
@@ -143,8 +143,17 @@ jobs:
|
|||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
dotnet restore
|
dotnet restore
|
||||||
|
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Last command failed."
|
||||||
|
}
|
||||||
|
|
||||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
||||||
|
|
||||||
|
if ($LastExitCode -ne 0) {
|
||||||
|
throw "Last command failed."
|
||||||
|
}
|
||||||
|
|
||||||
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
|
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
|
||||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||||
env:
|
env:
|
||||||
@@ -396,7 +405,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
- name: Upload ASF-${{ matrix.variant }}
|
- name: Upload ASF-${{ matrix.variant }}
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
|
name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
|
||||||
path: out/ASF-${{ matrix.variant }}.zip
|
path: out/ASF-${{ matrix.variant }}.zip
|
||||||
@@ -406,6 +415,9 @@ jobs:
|
|||||||
needs: publish-asf
|
needs: publish-asf
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
@@ -413,55 +425,55 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: Download ASF-generic artifact from ubuntu-latest
|
- name: Download ASF-generic artifact from ubuntu-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: ubuntu-latest_ASF-generic
|
name: ubuntu-latest_ASF-generic
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-linux-arm artifact from ubuntu-latest
|
- name: Download ASF-linux-arm artifact from ubuntu-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: ubuntu-latest_ASF-linux-arm
|
name: ubuntu-latest_ASF-linux-arm
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
|
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: ubuntu-latest_ASF-linux-arm64
|
name: ubuntu-latest_ASF-linux-arm64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-linux-x64 artifact from ubuntu-latest
|
- name: Download ASF-linux-x64 artifact from ubuntu-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: ubuntu-latest_ASF-linux-x64
|
name: ubuntu-latest_ASF-linux-x64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-osx-arm64 artifact from macos-latest
|
- name: Download ASF-osx-arm64 artifact from macos-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: macos-latest_ASF-osx-arm64
|
name: macos-latest_ASF-osx-arm64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-osx-x64 artifact from macos-latest
|
- name: Download ASF-osx-x64 artifact from macos-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: macos-latest_ASF-osx-x64
|
name: macos-latest_ASF-osx-x64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-win-arm64 artifact from windows-latest
|
- name: Download ASF-win-arm64 artifact from windows-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: windows-latest_ASF-win-arm64
|
name: windows-latest_ASF-win-arm64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Download ASF-win-x64 artifact from windows-latest
|
- name: Download ASF-win-x64 artifact from windows-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v4.1.2
|
||||||
with:
|
with:
|
||||||
name: windows-latest_ASF-win-x64
|
name: windows-latest_ASF-win-x64
|
||||||
path: out
|
path: out
|
||||||
|
|
||||||
- name: Import GPG key for signing
|
- name: Import GPG key for signing
|
||||||
uses: crazy-max/ghaction-import-gpg@v6.0.0
|
uses: crazy-max/ghaction-import-gpg@v6.1.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||||
|
|
||||||
@@ -478,19 +490,19 @@ jobs:
|
|||||||
)
|
)
|
||||||
|
|
||||||
- name: Upload SHA512SUMS
|
- name: Upload SHA512SUMS
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: SHA512SUMS
|
name: SHA512SUMS
|
||||||
path: out/SHA512SUMS
|
path: out/SHA512SUMS
|
||||||
|
|
||||||
- name: Upload SHA512SUMS.sign
|
- name: Upload SHA512SUMS.sign
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: SHA512SUMS.sign
|
name: SHA512SUMS.sign
|
||||||
path: out/SHA512SUMS.sign
|
path: out/SHA512SUMS.sign
|
||||||
|
|
||||||
- name: Create ArchiSteamFarm GitHub release
|
- name: Create ArchiSteamFarm GitHub release
|
||||||
uses: ncipollo/release-action@v1.13.0
|
uses: ncipollo/release-action@v1.14.0
|
||||||
with:
|
with:
|
||||||
artifacts: "out/*"
|
artifacts: "out/*"
|
||||||
bodyFile: .github/RELEASE_TEMPLATE.md
|
bodyFile: .github/RELEASE_TEMPLATE.md
|
||||||
|
|||||||
7
.github/workflows/translations.yml
vendored
7
.github/workflows/translations.yml
vendored
@@ -4,6 +4,9 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '55 1 * * *'
|
- cron: '55 1 * * *'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update:
|
update:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -27,7 +30,7 @@ jobs:
|
|||||||
git reset --hard origin/master
|
git reset --hard origin/master
|
||||||
|
|
||||||
- name: Download latest translations from Crowdin
|
- name: Download latest translations from Crowdin
|
||||||
uses: crowdin/github-action@v1.15.2
|
uses: crowdin/github-action@v1.19.0
|
||||||
with:
|
with:
|
||||||
upload_sources: false
|
upload_sources: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
@@ -39,7 +42,7 @@ jobs:
|
|||||||
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
|
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
|
||||||
|
|
||||||
- name: Import GPG key for signing
|
- name: Import GPG key for signing
|
||||||
uses: crazy-max/ghaction-import-gpg@v6.0.0
|
uses: crazy-max/ghaction-import-gpg@v6.1.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||||
git_config_global: true
|
git_config_global: true
|
||||||
|
|||||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 8b1afc3b23...f916fb3405
@@ -6,7 +6,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
|
||||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,16 +21,17 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.Plugins.Interfaces;
|
using ArchiSteamFarm.Plugins.Interfaces;
|
||||||
using ArchiSteamFarm.Steam;
|
using ArchiSteamFarm.Steam;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||||
@@ -45,31 +46,35 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
|||||||
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
|
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
|
||||||
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
|
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
|
||||||
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public string Name => nameof(ExamplePlugin);
|
public string Name => nameof(ExamplePlugin);
|
||||||
|
|
||||||
// This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to
|
// This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to
|
||||||
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
// Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public)
|
// Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public)
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
public bool CustomIsEnabledField { get; private set; } = true;
|
[Required]
|
||||||
|
public bool CustomIsEnabledField { get; private init; } = true;
|
||||||
|
|
||||||
// This method, apart from being called before any bot initialization takes place, allows you to read custom global config properties that are not recognized by ASF
|
// This method, apart from being called before any bot initialization takes place, allows you to read custom global config properties that are not recognized by ASF
|
||||||
// Thanks to that, you can extend default ASF config with your own stuff, then parse it here in order to customize your plugin during runtime
|
// Thanks to that, you can extend default ASF config with your own stuff, then parse it here in order to customize your plugin during runtime
|
||||||
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
||||||
// In addition to that, this method also guarantees that all plugins were already OnLoaded(), which allows cross-plugins-communication to be possible
|
// In addition to that, this method also guarantees that all plugins were already OnLoaded(), which allows cross-plugins-communication to be possible
|
||||||
public Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||||
if (additionalConfigProperties == null) {
|
if (additionalConfigProperties == null) {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
|
foreach ((string configProperty, JsonElement configValue) in additionalConfigProperties) {
|
||||||
// It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future
|
// It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future
|
||||||
switch (configProperty) {
|
switch (configProperty) {
|
||||||
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.Type == JTokenType.Boolean:
|
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.ValueKind == JsonValueKind.True:
|
||||||
bool exampleBooleanValue = configValue.Value<bool>();
|
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of true");
|
||||||
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of: {exampleBooleanValue}");
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -135,7 +140,7 @@ internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection,
|
|||||||
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
||||||
// Also keep in mind that this function can be called multiple times, e.g. when user edits their bot configs during runtime
|
// Also keep in mind that this function can be called multiple times, e.g. when user edits their bot configs during runtime
|
||||||
// Take a look at OnASFInit() for example parsing code
|
// Take a look at OnASFInit() for example parsing code
|
||||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||||
// For example, we'll ensure that every bot starts paused regardless of Paused property, in order to do this, we'll just call Pause here in InitModules()
|
// For example, we'll ensure that every bot starts paused regardless of Paused property, in order to do this, we'll just call Pause here in InitModules()
|
||||||
// Thanks to the fact that this method is called with each bot config reload, we'll ensure that our bot stays paused even if it'd get unpaused otherwise
|
// Thanks to the fact that this method is called with each bot config reload, we'll ensure that our bot stays paused even if it'd get unpaused otherwise
|
||||||
bot.ArchiLogger.LogGenericInfo("Pausing this bot as asked from the plugin");
|
bot.ArchiLogger.LogGenericInfo("Pausing this bot as asked from the plugin");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,15 +21,17 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||||
|
|
||||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
internal sealed class MeowResponse {
|
internal sealed class MeowResponse {
|
||||||
[JsonProperty("url", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly Uri URL = null!;
|
[JsonPropertyName("url")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Uri URL { get; private init; } = null!;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
private MeowResponse() { }
|
private MeowResponse() { }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,8 +20,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.Runtime;
|
using System.Runtime;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
@@ -38,8 +40,12 @@ internal sealed class PeriodicGCPlugin : IPlugin {
|
|||||||
private static readonly object LockObject = new();
|
private static readonly object LockObject = new();
|
||||||
private static readonly Timer PeriodicGCTimer = new(PerformGC);
|
private static readonly Timer PeriodicGCTimer = new(PerformGC);
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public string Name => nameof(PeriodicGCPlugin);
|
public string Name => nameof(PeriodicGCPlugin);
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
public Task OnLoaded() {
|
public Task OnLoaded() {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
<PackageReference Include="AngleSharp.XPath" IncludeAssets="compile" />
|
<PackageReference Include="AngleSharp.XPath" IncludeAssets="compile" />
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
|
||||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,11 +20,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||||
|
|
||||||
public sealed class SignInWithSteamRequest {
|
public sealed class SignInWithSteamRequest {
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
public Uri RedirectURL { get; private set; } = null!;
|
[JsonRequired]
|
||||||
|
public Uri RedirectURL { get; private init; } = null!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,13 +20,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||||
|
|
||||||
public sealed class SignInWithSteamResponse {
|
public sealed class SignInWithSteamResponse {
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
public Uri ReturnURL { get; private set; }
|
[JsonRequired]
|
||||||
|
public Uri ReturnURL { get; private init; }
|
||||||
|
|
||||||
internal SignInWithSteamResponse(Uri returnURL) {
|
internal SignInWithSteamResponse(Uri returnURL) {
|
||||||
ArgumentNullException.ThrowIfNull(returnURL);
|
ArgumentNullException.ThrowIfNull(returnURL);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.Plugins.Interfaces;
|
using ArchiSteamFarm.Plugins.Interfaces;
|
||||||
@@ -31,8 +33,12 @@ namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam;
|
|||||||
[Export(typeof(IPlugin))]
|
[Export(typeof(IPlugin))]
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
internal sealed class SignInWithSteamPlugin : IPlugin {
|
internal sealed class SignInWithSteamPlugin : IPlugin {
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public string Name => nameof(SignInWithSteamPlugin);
|
public string Name => nameof(SignInWithSteamPlugin);
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public Version Version => typeof(SignInWithSteamPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public Version Version => typeof(SignInWithSteamPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
public Task OnLoaded() {
|
public Task OnLoaded() {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
|
||||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -22,7 +22,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.IPC.Responses;
|
using ArchiSteamFarm.IPC.Responses;
|
||||||
@@ -38,16 +41,15 @@ using SteamKit2;
|
|||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||||
|
|
||||||
internal static class Backend {
|
internal static class Backend {
|
||||||
internal static async Task<BasicResponse?> AnnounceForListing(ulong steamID, WebBrowser webBrowser, IReadOnlyList<AssetForListing> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
|
||||||
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
|
|
||||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
ArgumentNullException.ThrowIfNull(inventory);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||||
if ((inventory == null) || (inventory.Count == 0)) {
|
|
||||||
throw new ArgumentNullException(nameof(inventory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||||
@@ -60,11 +62,71 @@ internal static class Backend {
|
|||||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri request = new(ArchiNet.URL, "/Api/Listing/Announce/v3");
|
ArgumentNullException.ThrowIfNull(inventoryRemoved);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(previousInventoryChecksum);
|
||||||
|
|
||||||
AnnouncementRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), steamID, tradeToken, inventory, acceptedMatchableTypes, totalInventoryCount, matchEverything, ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration, nickname, avatarHash);
|
Uri request = new(ArchiNet.URL, "/Api/Listing/AnnounceDiff/v2");
|
||||||
|
|
||||||
return await webBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
AnnouncementDiffRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), steamID, inventory, inventoryChecksum, acceptedMatchableTypes, totalInventoryCount, matchEverything, ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration, tradeToken, inventoryRemoved, previousInventoryChecksum, nickname, avatarHash);
|
||||||
|
|
||||||
|
return await webBrowser.UrlPostToJsonObject<GenericResponse<BackgroundTaskResponse>, AnnouncementDiffRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||||
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((inventory == null) || (inventory.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(inventory));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||||
|
|
||||||
|
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
||||||
|
|
||||||
|
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri request = new(ArchiNet.URL, "/Api/Listing/Announce/v5");
|
||||||
|
|
||||||
|
AnnouncementRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), steamID, inventory, inventoryChecksum, acceptedMatchableTypes, totalInventoryCount, matchEverything, ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration, tradeToken, nickname, avatarHash);
|
||||||
|
|
||||||
|
return await webBrowser.UrlPostToJsonObject<GenericResponse<BackgroundTaskResponse>, AnnouncementRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GenerateChecksumFor(IList<AssetForListing> assetsForListings) {
|
||||||
|
if ((assetsForListings == null) || (assetsForListings.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(assetsForListings));
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = string.Join('|', assetsForListings.Select(static asset => asset.BackendHashCode));
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
|
||||||
|
return Utilities.GenerateChecksumFor(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task<HttpStatusCode?> GetLicenseStatus(Guid licenseID, WebBrowser webBrowser) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
|
||||||
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
|
|
||||||
|
Uri request = new(ArchiNet.URL, "/Api/Licenses/Status");
|
||||||
|
|
||||||
|
Dictionary<string, string> headers = new(1, StringComparer.Ordinal) {
|
||||||
|
{ "X-License-Key", licenseID.ToString("N") }
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectResponse<GenericResponse>? response = await webBrowser.UrlGetToJsonObject<GenericResponse>(request, headers, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return response?.StatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||||
@@ -97,6 +159,28 @@ internal static class Backend {
|
|||||||
return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet<ListedUser>.Empty);
|
return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet<ListedUser>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
|
||||||
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(matchableTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((realAppIDs == null) || (realAppIDs.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(realAppIDs));
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri request = new(ArchiNet.URL, "/Api/SetParts/Request");
|
||||||
|
|
||||||
|
SetPartsRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), steamID, matchableTypes, realAppIDs);
|
||||||
|
|
||||||
|
return await webBrowser.UrlPostToJsonObject<GenericResponse<ImmutableHashSet<SetPart>>, SetPartsRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<BasicResponse?> HeartBeatForListing(Bot bot, WebBrowser webBrowser) {
|
internal static async Task<BasicResponse?> HeartBeatForListing(Bot bot, WebBrowser webBrowser) {
|
||||||
ArgumentNullException.ThrowIfNull(bot);
|
ArgumentNullException.ThrowIfNull(bot);
|
||||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
@@ -107,4 +191,22 @@ internal static class Backend {
|
|||||||
|
|
||||||
return await webBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
return await webBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> PollResult(WebBrowser webBrowser, ulong steamID, Guid requestID) {
|
||||||
|
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentOutOfRangeException.ThrowIfEqual(requestID, Guid.Empty);
|
||||||
|
|
||||||
|
if (SharedInfo.BuildInfo.IsCustomBuild) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri request = new(ArchiNet.URL, $"/Api/Listing/PollResult/{steamID}/{requestID:N}");
|
||||||
|
|
||||||
|
return await webBrowser.UrlGetToJsonObject<GenericResponse<BackgroundTaskResponse>>(request, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
166
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/BotCache.cs
Normal file
166
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/BotCache.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ArchiSteamFarm.Collections;
|
||||||
|
using ArchiSteamFarm.Core;
|
||||||
|
using ArchiSteamFarm.Helpers;
|
||||||
|
using ArchiSteamFarm.Helpers.Json;
|
||||||
|
using ArchiSteamFarm.Localization;
|
||||||
|
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||||
|
|
||||||
|
internal sealed class BotCache : SerializableFile {
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
internal ConcurrentList<AssetForListing> LastAnnouncedAssetsForListing { get; private init; } = [];
|
||||||
|
|
||||||
|
internal string? LastAnnouncedTradeToken {
|
||||||
|
get => BackingLastAnnouncedTradeToken;
|
||||||
|
|
||||||
|
set {
|
||||||
|
if (BackingLastAnnouncedTradeToken == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackingLastAnnouncedTradeToken = value;
|
||||||
|
Utilities.InBackground(Save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string? LastInventoryChecksumBeforeDeduplication {
|
||||||
|
get => BackingLastInventoryChecksumBeforeDeduplication;
|
||||||
|
|
||||||
|
set {
|
||||||
|
if (BackingLastInventoryChecksumBeforeDeduplication == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackingLastInventoryChecksumBeforeDeduplication = value;
|
||||||
|
Utilities.InBackground(Save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DateTime? LastRequestAt {
|
||||||
|
get => BackingLastRequestAt;
|
||||||
|
|
||||||
|
set {
|
||||||
|
if (BackingLastRequestAt == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackingLastRequestAt = value;
|
||||||
|
Utilities.InBackground(Save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
private string? BackingLastAnnouncedTradeToken { get; set; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
private string? BackingLastInventoryChecksumBeforeDeduplication { get; set; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
private DateTime? BackingLastRequestAt { get; set; }
|
||||||
|
|
||||||
|
private BotCache(string filePath) : this() {
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||||
|
|
||||||
|
FilePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private BotCache() => LastAnnouncedAssetsForListing.OnModified += OnObjectModified;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public bool ShouldSerializeBackingLastAnnouncedTradeToken() => !string.IsNullOrEmpty(BackingLastAnnouncedTradeToken);
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public bool ShouldSerializeBackingLastInventoryChecksumBeforeDeduplication() => !string.IsNullOrEmpty(BackingLastInventoryChecksumBeforeDeduplication);
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public bool ShouldSerializeBackingLastRequestAt() => BackingLastRequestAt.HasValue;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public bool ShouldSerializeLastAnnouncedAssetsForListing() => LastAnnouncedAssetsForListing.Count > 0;
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing) {
|
||||||
|
if (disposing) {
|
||||||
|
// Events we registered
|
||||||
|
LastAnnouncedAssetsForListing.OnModified -= OnObjectModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base dispose
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task Save() => Save(this);
|
||||||
|
|
||||||
|
internal static async Task<BotCache> CreateOrLoad(string filePath) {
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath)) {
|
||||||
|
return new BotCache(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
BotCache? botCache;
|
||||||
|
|
||||||
|
try {
|
||||||
|
string json = await File.ReadAllTextAsync(filePath).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(json)) {
|
||||||
|
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||||
|
|
||||||
|
return new BotCache(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
botCache = json.ToJsonObject<BotCache>();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ASF.ArchiLogger.LogGenericException(e);
|
||||||
|
|
||||||
|
return new BotCache(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botCache == null) {
|
||||||
|
ASF.ArchiLogger.LogNullError(botCache);
|
||||||
|
|
||||||
|
return new BotCache(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
botCache.FilePath = filePath;
|
||||||
|
|
||||||
|
return botCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnObjectModified(object? sender, EventArgs e) {
|
||||||
|
if (string.IsNullOrEmpty(FilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Save().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -80,12 +80,20 @@ internal static class Commands {
|
|||||||
return access > EAccess.None ? bot.Commands.FormatBotResponse(Strings.ErrorAccessDenied) : null;
|
return access > EAccess.None ? bot.Commands.FormatBotResponse(Strings.ErrorAccessDenied) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bot.IsConnectedAndLoggedOn) {
|
||||||
|
return bot.Commands.FormatBotResponse(Strings.BotNotConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything)) {
|
||||||
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(BotConfig.ETradingPreferences.MatchEverything)));
|
||||||
|
}
|
||||||
|
|
||||||
if ((ASF.GlobalConfig?.LicenseID == null) || (ASF.GlobalConfig.LicenseID == Guid.Empty)) {
|
if ((ASF.GlobalConfig?.LicenseID == null) || (ASF.GlobalConfig.LicenseID == Guid.Empty)) {
|
||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ASF.GlobalConfig.LicenseID)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ASF.GlobalConfig.LicenseID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || !ItemsMatcherPlugin.RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
|
if (!ItemsMatcherPlugin.RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
|
||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(BotConfig.ETradingPreferences.MatchActively)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(remoteCommunication)));
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteCommunication.TriggerMatchActivelyEarlier();
|
remoteCommunication.TriggerMatchActivelyEarlier();
|
||||||
@@ -112,7 +120,7 @@ internal static class Commands {
|
|||||||
|
|
||||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => ResponseMatch(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot)))).ConfigureAwait(false);
|
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => ResponseMatch(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot)))).ConfigureAwait(false);
|
||||||
|
|
||||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
List<string> responses = [..results.Where(static result => !string.IsNullOrEmpty(result))];
|
||||||
|
|
||||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using ArchiSteamFarm.Steam.Data;
|
||||||
|
using ArchiSteamFarm.Steam.Storage;
|
||||||
|
using SteamKit2;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
|
internal sealed class AnnouncementDiffRequest : AnnouncementRequest {
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
private ImmutableHashSet<AssetForListing> InventoryRemoved { get; init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
private string PreviousInventoryChecksum { get; init; }
|
||||||
|
|
||||||
|
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(inventory);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||||
|
|
||||||
|
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(matchableTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
||||||
|
|
||||||
|
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(inventoryRemoved);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(previousInventoryChecksum);
|
||||||
|
|
||||||
|
InventoryRemoved = inventoryRemoved.ToImmutableHashSet();
|
||||||
|
PreviousInventoryChecksum = previousInventoryChecksum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -22,72 +22,83 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using ArchiSteamFarm.Steam.Storage;
|
using ArchiSteamFarm.Steam.Storage;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal sealed class AnnouncementRequest {
|
internal class AnnouncementRequest {
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
private readonly string? AvatarHash;
|
private string? AvatarHash { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly Guid Guid;
|
[JsonRequired]
|
||||||
|
private Guid Guid { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly ImmutableList<AssetForListing> Inventory;
|
[JsonRequired]
|
||||||
|
private ImmutableHashSet<AssetForListing> Inventory { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
[JsonRequired]
|
||||||
|
private string InventoryChecksum { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly bool MatchEverything;
|
[JsonRequired]
|
||||||
|
private ImmutableHashSet<Asset.EType> MatchableTypes { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly byte MaxTradeHoldDuration;
|
[JsonRequired]
|
||||||
|
private bool MatchEverything { get; init; }
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
private readonly string? Nickname;
|
[JsonRequired]
|
||||||
|
private byte MaxTradeHoldDuration { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly ulong SteamID;
|
private string? Nickname { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly uint TotalInventoryCount;
|
[JsonRequired]
|
||||||
|
private ulong SteamID { get; init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
private readonly string TradeToken;
|
[JsonRequired]
|
||||||
|
private uint TotalInventoryCount { get; init; }
|
||||||
|
|
||||||
internal AnnouncementRequest(Guid guid, ulong steamID, string tradeToken, IReadOnlyList<AssetForListing> inventory, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string? nickname = null, string? avatarHash = null) {
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
private string TradeToken { get; init; }
|
||||||
|
|
||||||
|
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||||
|
|
||||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
ArgumentNullException.ThrowIfNull(inventory);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((inventory == null) || (inventory.Count == 0)) {
|
|
||||||
throw new ArgumentNullException(nameof(inventory));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||||
throw new ArgumentNullException(nameof(matchableTypes));
|
throw new ArgumentNullException(nameof(matchableTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
||||||
|
|
||||||
|
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||||
|
}
|
||||||
|
|
||||||
Guid = guid;
|
Guid = guid;
|
||||||
SteamID = steamID;
|
SteamID = steamID;
|
||||||
TradeToken = tradeToken;
|
TradeToken = tradeToken;
|
||||||
Inventory = inventory.ToImmutableList();
|
Inventory = inventory.ToImmutableHashSet();
|
||||||
|
InventoryChecksum = inventoryChecksum;
|
||||||
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
||||||
MatchEverything = matchEverything;
|
MatchEverything = matchEverything;
|
||||||
MaxTradeHoldDuration = maxTradeHoldDuration;
|
MaxTradeHoldDuration = maxTradeHoldDuration;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,18 +20,31 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal sealed class AssetForListing : AssetInInventory {
|
internal sealed class AssetForListing : AssetInInventory {
|
||||||
[JsonProperty("l", Required = Required.Always)]
|
internal string BackendHashCode => $"{Index}-{PreviousAssetID}-{AssetID}-{ClassID}-{Rarity}-{RealAppID}-{Tradable}-{Type}-{Amount}";
|
||||||
internal readonly ulong PreviousAssetID;
|
|
||||||
|
|
||||||
internal AssetForListing(Asset asset, ulong previousAssetID) : base(asset) {
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("i")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal uint Index { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("l")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong PreviousAssetID { get; private init; }
|
||||||
|
|
||||||
|
internal AssetForListing(Asset asset, uint index, ulong previousAssetID) : base(asset) {
|
||||||
ArgumentNullException.ThrowIfNull(asset);
|
ArgumentNullException.ThrowIfNull(asset);
|
||||||
|
|
||||||
|
Index = index;
|
||||||
PreviousAssetID = previousAssetID;
|
PreviousAssetID = previousAssetID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private AssetForListing() { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,29 +20,41 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal class AssetForMatching {
|
internal class AssetForMatching {
|
||||||
[JsonProperty("a", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly uint Amount;
|
[JsonPropertyName("a")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal uint Amount { get; set; }
|
||||||
|
|
||||||
[JsonProperty("c", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong ClassID;
|
[JsonPropertyName("c")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong ClassID { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("r", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly Asset.ERarity Rarity;
|
[JsonPropertyName("r")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Asset.ERarity Rarity { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("e", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly uint RealAppID;
|
[JsonPropertyName("e")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal uint RealAppID { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("t", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly bool Tradable;
|
[JsonPropertyName("t")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal bool Tradable { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("p", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly Asset.EType Type;
|
[JsonPropertyName("p")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Asset.EType Type { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
protected AssetForMatching() { }
|
protected AssetForMatching() { }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,14 +20,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal class AssetInInventory : AssetForMatching {
|
internal class AssetInInventory : AssetForMatching {
|
||||||
[JsonProperty("d", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong AssetID;
|
[JsonPropertyName("d")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong AssetID { get; private init; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
protected AssetInInventory() { }
|
||||||
|
|
||||||
internal AssetInInventory(Asset asset) : base(asset) {
|
internal AssetInInventory(Asset asset) : base(asset) {
|
||||||
ArgumentNullException.ThrowIfNull(asset);
|
ArgumentNullException.ThrowIfNull(asset);
|
||||||
@@ -35,8 +40,5 @@ internal class AssetInInventory : AssetForMatching {
|
|||||||
AssetID = asset.AssetID;
|
AssetID = asset.AssetID;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
private AssetInInventory() { }
|
|
||||||
|
|
||||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, tradable: Tradable, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, tradable: Tradable, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// _ _ _ ____ _ _____
|
// _ _ _ ____ _ _____
|
||||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -19,25 +19,24 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.Steam.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
internal sealed class AccessTokenResponse : ResultResponse {
|
internal sealed class BackgroundTaskResponse {
|
||||||
[JsonProperty("data", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly AccessTokenData Data = new();
|
[JsonRequired]
|
||||||
|
internal bool Finished { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Guid RequestID { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
private AccessTokenResponse() { }
|
private BackgroundTaskResponse() { }
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
|
||||||
internal sealed class AccessTokenData {
|
|
||||||
[JsonProperty("webapi_token", Required = Required.Always)]
|
|
||||||
internal readonly string WebAPIToken = "";
|
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
internal AccessTokenData() { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,17 +20,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal sealed class HeartBeatRequest {
|
internal sealed class HeartBeatRequest {
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly Guid Guid;
|
[JsonRequired]
|
||||||
|
internal Guid Guid { get; private init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong SteamID;
|
[JsonRequired]
|
||||||
|
internal ulong SteamID { get; private init; }
|
||||||
|
|
||||||
internal HeartBeatRequest(Guid guid, ulong steamID) {
|
internal HeartBeatRequest(Guid guid, ulong steamID) {
|
||||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -23,24 +23,28 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
internal sealed class InventoriesRequest {
|
internal sealed class InventoriesRequest {
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly Guid Guid;
|
[JsonRequired]
|
||||||
|
internal Guid Guid { get; private init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<AssetForMatching> Inventory;
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<AssetForMatching> Inventory { get; private init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong SteamID;
|
[JsonRequired]
|
||||||
|
internal ulong SteamID { get; private init; }
|
||||||
|
|
||||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
||||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,47 +21,48 @@
|
|||||||
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Steam.Data;
|
using ArchiSteamFarm.Steam.Data;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
internal sealed class ListedUser {
|
internal sealed class ListedUser {
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<AssetInInventory> Assets = ImmutableHashSet<AssetInInventory>.Empty;
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<AssetInInventory> Assets { get; private init; } = ImmutableHashSet<AssetInInventory>.Empty;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes = ImmutableHashSet<Asset.EType>.Empty;
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = ImmutableHashSet<Asset.EType>.Empty;
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonRequired]
|
||||||
internal readonly bool MatchEverything;
|
internal bool MatchEverything { get; private init; }
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonRequired]
|
||||||
internal readonly byte MaxTradeHoldDuration;
|
internal byte MaxTradeHoldDuration { get; private init; }
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty(Required = Required.AllowNull)]
|
internal string? Nickname { get; private init; }
|
||||||
internal readonly string? Nickname;
|
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonRequired]
|
||||||
internal readonly ulong SteamID;
|
internal ulong SteamID { get; private init; }
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonRequired]
|
||||||
internal readonly uint TotalInventoryCount;
|
internal uint TotalGamesCount { get; private init; }
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string TradeToken = "";
|
[JsonRequired]
|
||||||
|
internal uint TotalInventoryCount { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string TradeToken { get; private init; } = "";
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
private ListedUser() { }
|
private ListedUser() { }
|
||||||
|
|||||||
54
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs
Normal file
54
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Data/SetPart.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using ArchiSteamFarm.Steam.Data;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
|
internal sealed class SetPart {
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("c")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong ClassID { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("r")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Asset.ERarity Rarity { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("e")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal uint RealAppID { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("p")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Asset.EType Type { get; private init; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private SetPart() { }
|
||||||
|
}
|
||||||
|
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using ArchiSteamFarm.Steam.Data;
|
||||||
|
using SteamKit2;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||||
|
|
||||||
|
internal sealed class SetPartsRequest {
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal Guid Guid { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> RealAppIDs { get; private init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong SteamID { get; private init; }
|
||||||
|
|
||||||
|
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(matchableTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((realAppIDs == null) || (realAppIDs.Count == 0)) {
|
||||||
|
throw new ArgumentNullException(nameof(realAppIDs));
|
||||||
|
}
|
||||||
|
|
||||||
|
Guid = guid;
|
||||||
|
SteamID = steamID;
|
||||||
|
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
||||||
|
RealAppIDs = realAppIDs.ToImmutableHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -23,8 +23,11 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
|
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
|
||||||
@@ -33,8 +36,6 @@ using ArchiSteamFarm.Plugins.Interfaces;
|
|||||||
using ArchiSteamFarm.Steam;
|
using ArchiSteamFarm.Steam;
|
||||||
using ArchiSteamFarm.Steam.Exchange;
|
using ArchiSteamFarm.Steam.Exchange;
|
||||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||||
@@ -43,10 +44,12 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
|||||||
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotTradeOfferResults, IBotUserNotifications {
|
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotTradeOfferResults, IBotUserNotifications {
|
||||||
internal static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
|
internal static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override string Name => nameof(ItemsMatcherPlugin);
|
public override string Name => nameof(ItemsMatcherPlugin);
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override Version Version => typeof(ItemsMatcherPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public override Version Version => typeof(ItemsMatcherPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||||
@@ -83,7 +86,7 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||||
ArgumentNullException.ThrowIfNull(bot);
|
ArgumentNullException.ThrowIfNull(bot);
|
||||||
|
|
||||||
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
||||||
|
|||||||
@@ -62,4 +62,8 @@
|
|||||||
PublicKeyToken=b77a5c561934e089
|
PublicKeyToken=b77a5c561934e089
|
||||||
</value>
|
</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||||
|
<value>Neke konfirmacije su neuspješne, odprilike {0} od {1} razmjena je poslano uspješno.</value>
|
||||||
|
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -66,4 +66,8 @@
|
|||||||
<value>{0} sets ont été matché pendant ce round.</value>
|
<value>{0} sets ont été matché pendant ce round.</value>
|
||||||
<comment>{0} will be replaced by number of sets traded</comment>
|
<comment>{0} will be replaced by number of sets traded</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||||
|
<value>Certaines confirmations ont échoué, environ {0} sur les transactions {1} ont été envoyées avec succès.</value>
|
||||||
|
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -66,4 +66,16 @@
|
|||||||
<value>Abbinati un totale di {0} set questo round.</value>
|
<value>Abbinati un totale di {0} set questo round.</value>
|
||||||
<comment>{0} will be replaced by number of sets traded</comment>
|
<comment>{0} will be replaced by number of sets traded</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MatchingFound" xml:space="preserve">
|
||||||
|
<value>Abbinato un totale di {0} elementi tramite bot {1} ({2}), inviando un'offerta commerciale...</value>
|
||||||
|
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>
|
||||||
|
</data>
|
||||||
|
<data name="TradeOfferFailed" xml:space="preserve">
|
||||||
|
<value>Impossibile inviare un'offerta di scambio al bot {0} ({1}), proseguendo...</value>
|
||||||
|
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
|
||||||
|
</data>
|
||||||
|
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||||
|
<value>Alcune conferme sono fallite, circa {0} su {1} sono state inviate con successo.</value>
|
||||||
|
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -66,4 +66,20 @@
|
|||||||
<value>Celkovo porovnaných {0} sad karet.</value>
|
<value>Celkovo porovnaných {0} sad karet.</value>
|
||||||
<comment>{0} will be replaced by number of sets traded</comment>
|
<comment>{0} will be replaced by number of sets traded</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ListingAnnouncing" xml:space="preserve">
|
||||||
|
<value>Oznámenie {0} ({1}) s inventárom v ktorom je {2} položiek na zozname...</value>
|
||||||
|
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname, {2} will be replaced with number of items in the inventory</comment>
|
||||||
|
</data>
|
||||||
|
<data name="MatchingFound" xml:space="preserve">
|
||||||
|
<value>Zhodné s celkom {0} položiek s botom {1} ({2}), posielanie obchodnej ponuky...</value>
|
||||||
|
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>
|
||||||
|
</data>
|
||||||
|
<data name="TradeOfferFailed" xml:space="preserve">
|
||||||
|
<value>Nepodarilo se odoslať obchodnú ponuku pre bota {0} ({1}), pokračujem...</value>
|
||||||
|
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
|
||||||
|
</data>
|
||||||
|
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||||
|
<value>Niektoré potvrdenia sa nepodarili, úspešne bolo poslaných približne {0} z {1} obchodov.</value>
|
||||||
|
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
|
||||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -27,9 +27,9 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
|
using ArchiSteamFarm.Helpers.Json;
|
||||||
using ArchiSteamFarm.Localization;
|
using ArchiSteamFarm.Localization;
|
||||||
using ArchiSteamFarm.Steam;
|
using ArchiSteamFarm.Steam;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
using SteamKit2.Internal;
|
using SteamKit2.Internal;
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ internal static class Commands {
|
|||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = JsonConvert.DeserializeObject<Steam.Security.MobileAuthenticator>(json);
|
Steam.Security.MobileAuthenticator? mobileAuthenticator = json.ToJsonObject<Steam.Security.MobileAuthenticator>();
|
||||||
|
|
||||||
if (mobileAuthenticator == null) {
|
if (mobileAuthenticator == null) {
|
||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||||
@@ -220,7 +220,7 @@ internal static class Commands {
|
|||||||
|
|
||||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorFinalize(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot, activationCode))).ConfigureAwait(false);
|
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorFinalize(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot, activationCode))).ConfigureAwait(false);
|
||||||
|
|
||||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
List<string> responses = [..results.Where(static result => !string.IsNullOrEmpty(result))];
|
||||||
|
|
||||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ internal static class Commands {
|
|||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = JsonConvert.DeserializeObject<Steam.Security.MobileAuthenticator>(json);
|
Steam.Security.MobileAuthenticator? mobileAuthenticator = json.ToJsonObject<Steam.Security.MobileAuthenticator>();
|
||||||
|
|
||||||
if (mobileAuthenticator == null) {
|
if (mobileAuthenticator == null) {
|
||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||||
@@ -313,7 +313,7 @@ internal static class Commands {
|
|||||||
|
|
||||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorFinalized(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot, activationCode))).ConfigureAwait(false);
|
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorFinalized(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot, activationCode))).ConfigureAwait(false);
|
||||||
|
|
||||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
List<string> responses = [..results.Where(static result => !string.IsNullOrEmpty(result))];
|
||||||
|
|
||||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||||
}
|
}
|
||||||
@@ -357,10 +357,10 @@ internal static class Commands {
|
|||||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result));
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
MaFileData maFileData = new(response, deviceID);
|
MaFileData maFileData = new(response, bot.SteamID, deviceID);
|
||||||
|
|
||||||
string maFilePendingPath = $"{bot.GetFilePath(Bot.EFileType.MobileAuthenticator)}.PENDING";
|
string maFilePendingPath = $"{bot.GetFilePath(Bot.EFileType.MobileAuthenticator)}.PENDING";
|
||||||
string json = JsonConvert.SerializeObject(maFileData, Formatting.Indented);
|
string json = maFileData.ToJsonText(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await File.WriteAllTextAsync(maFilePendingPath, json).ConfigureAwait(false);
|
await File.WriteAllTextAsync(maFilePendingPath, json).ConfigureAwait(false);
|
||||||
@@ -392,7 +392,7 @@ internal static class Commands {
|
|||||||
|
|
||||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorInit(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot))).ConfigureAwait(false);
|
IList<string?> results = await Utilities.InParallel(bots.Select(bot => ResponseTwoFactorInit(Steam.Interaction.Commands.GetProxyAccess(bot, access, steamID), bot))).ConfigureAwait(false);
|
||||||
|
|
||||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
List<string> responses = [..results.Where(static result => !string.IsNullOrEmpty(result))];
|
||||||
|
|
||||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,47 +20,79 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
using SteamKit2;
|
||||||
using SteamKit2.Internal;
|
using SteamKit2.Internal;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||||
|
|
||||||
internal sealed class MaFileData {
|
internal sealed class MaFileData {
|
||||||
[JsonProperty("account_name", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string AccountName;
|
[JsonPropertyName("account_name")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string AccountName { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("device_id", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string DeviceID;
|
[JsonPropertyName("device_id")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string DeviceID { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("identity_secret", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string IdentitySecret;
|
[JsonPropertyName("identity_secret")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string IdentitySecret { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("revocation_code", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string RevocationCode;
|
[JsonPropertyName("revocation_code")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string RevocationCode { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("secret_1", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string Secret1;
|
[JsonPropertyName("secret_1")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string Secret1 { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("serial_number", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong SerialNumber;
|
[JsonPropertyName("serial_number")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong SerialNumber { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("server_time", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ulong ServerTime;
|
[JsonPropertyName("server_time")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong ServerTime { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("shared_secret", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string SharedSecret;
|
[JsonRequired]
|
||||||
|
internal MaFileSessionData Session { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("status", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly int Status;
|
[JsonPropertyName("shared_secret")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string SharedSecret { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("token_gid", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string TokenGid;
|
[JsonPropertyName("status")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal int Status { get; private init; }
|
||||||
|
|
||||||
[JsonProperty("uri", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly string Uri;
|
[JsonPropertyName("token_gid")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string TokenGid { get; private init; }
|
||||||
|
|
||||||
internal MaFileData(CTwoFactor_AddAuthenticator_Response data, string deviceID) {
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("uri")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal string Uri { get; private init; }
|
||||||
|
|
||||||
|
internal MaFileData(CTwoFactor_AddAuthenticator_Response data, ulong steamID, string deviceID) {
|
||||||
ArgumentNullException.ThrowIfNull(data);
|
ArgumentNullException.ThrowIfNull(data);
|
||||||
|
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(deviceID);
|
ArgumentException.ThrowIfNullOrEmpty(deviceID);
|
||||||
|
|
||||||
AccountName = data.account_name;
|
AccountName = data.account_name;
|
||||||
@@ -70,6 +102,7 @@ internal sealed class MaFileData {
|
|||||||
Secret1 = Convert.ToBase64String(data.secret_1);
|
Secret1 = Convert.ToBase64String(data.secret_1);
|
||||||
SerialNumber = data.serial_number;
|
SerialNumber = data.serial_number;
|
||||||
ServerTime = data.server_time;
|
ServerTime = data.server_time;
|
||||||
|
Session = new MaFileSessionData(steamID);
|
||||||
SharedSecret = Convert.ToBase64String(data.shared_secret);
|
SharedSecret = Convert.ToBase64String(data.shared_secret);
|
||||||
Status = data.status;
|
Status = data.status;
|
||||||
TokenGid = data.token_gid;
|
TokenGid = data.token_gid;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using SteamKit2;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||||
|
|
||||||
|
internal sealed class MaFileSessionData {
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ulong SteamID { get; private init; }
|
||||||
|
|
||||||
|
internal MaFileSessionData(ulong steamID) {
|
||||||
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamID = steamID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -22,15 +22,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.OfficialPlugins.MobileAuthenticator.Localization;
|
using ArchiSteamFarm.OfficialPlugins.MobileAuthenticator.Localization;
|
||||||
using ArchiSteamFarm.Plugins;
|
using ArchiSteamFarm.Plugins;
|
||||||
using ArchiSteamFarm.Plugins.Interfaces;
|
using ArchiSteamFarm.Plugins.Interfaces;
|
||||||
using ArchiSteamFarm.Steam;
|
using ArchiSteamFarm.Steam;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||||
@@ -38,10 +39,12 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
|||||||
[Export(typeof(IPlugin))]
|
[Export(typeof(IPlugin))]
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
|
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
|
||||||
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient {
|
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient {
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override string Name => nameof(MobileAuthenticatorPlugin);
|
public override string Name => nameof(MobileAuthenticatorPlugin);
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override Version Version => typeof(MobileAuthenticatorPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public override Version Version => typeof(MobileAuthenticatorPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
|
||||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -23,36 +23,52 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||||
|
|
||||||
internal sealed class SubmitRequest {
|
internal sealed class SubmitRequest {
|
||||||
[JsonProperty("guid", Required = Required.Always)]
|
|
||||||
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
|
|
||||||
|
|
||||||
[JsonProperty("token", Required = Required.Always)]
|
|
||||||
private static string Token => SharedInfo.Token;
|
|
||||||
|
|
||||||
[JsonProperty("v", Required = Required.Always)]
|
|
||||||
private static byte Version => SharedInfo.ApiVersion;
|
|
||||||
|
|
||||||
[JsonProperty("apps", Required = Required.Always)]
|
|
||||||
private readonly ImmutableDictionary<string, string> Apps;
|
|
||||||
|
|
||||||
[JsonProperty("depots", Required = Required.Always)]
|
|
||||||
private readonly ImmutableDictionary<string, string> Depots;
|
|
||||||
|
|
||||||
private readonly ulong SteamID;
|
private readonly ulong SteamID;
|
||||||
|
|
||||||
[JsonProperty("subs", Required = Required.Always)]
|
#pragma warning disable CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
private readonly ImmutableDictionary<string, string> Subs;
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("guid")]
|
||||||
|
private string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
|
||||||
|
#pragma warning restore CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
|
|
||||||
[JsonProperty("steamid", Required = Required.Always)]
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("steamid")]
|
||||||
private string SteamIDText => new SteamID(SteamID).Render();
|
private string SteamIDText => new SteamID(SteamID).Render();
|
||||||
|
|
||||||
|
#pragma warning disable CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("token")]
|
||||||
|
private string Token => SharedInfo.Token;
|
||||||
|
#pragma warning restore CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
|
|
||||||
|
#pragma warning disable CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("v")]
|
||||||
|
private byte Version => SharedInfo.ApiVersion;
|
||||||
|
#pragma warning restore CA1822 // We can't make it static, STJ doesn't serialize it otherwise
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("apps")]
|
||||||
|
[JsonRequired]
|
||||||
|
private ImmutableDictionary<string, string> Apps { get; init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("depots")]
|
||||||
|
[JsonRequired]
|
||||||
|
private ImmutableDictionary<string, string> Depots { get; init; }
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
[JsonPropertyName("subs")]
|
||||||
|
[JsonRequired]
|
||||||
|
private ImmutableDictionary<string, string> Subs { get; init; }
|
||||||
|
|
||||||
internal SubmitRequest(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
internal SubmitRequest(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
||||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,22 +20,21 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||||
|
|
||||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||||
internal sealed class SubmitResponse {
|
internal sealed class SubmitResponse {
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty("data", Required = Required.DisallowNull)]
|
[JsonPropertyName("data")]
|
||||||
internal readonly SubmitResponseData? Data;
|
internal SubmitResponseData? Data { get; private init; }
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
|
||||||
|
|
||||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
[JsonInclude]
|
||||||
[JsonProperty("success", Required = Required.Always)]
|
[JsonPropertyName("success")]
|
||||||
internal readonly bool Success;
|
[JsonRequired]
|
||||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
internal bool Success { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
private SubmitResponse() { }
|
private SubmitResponse() { }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,28 +20,40 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||||
|
|
||||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||||
internal sealed class SubmitResponseData {
|
internal sealed class SubmitResponseData {
|
||||||
[JsonProperty("new_apps", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("new_apps")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> NewApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty("new_depots", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("new_depots")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> NewDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty("new_subs", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("new_subs")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> NewPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty("verified_apps", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("verified_apps")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> VerifiedApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty("verified_depots", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("verified_depots")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> VerifiedDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty("verified_subs", Required = Required.Always)]
|
[JsonInclude]
|
||||||
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
|
[JsonPropertyName("verified_subs")]
|
||||||
|
[JsonRequired]
|
||||||
|
internal ImmutableHashSet<uint> VerifiedPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,52 +21,57 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Frozen;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.Helpers;
|
using ArchiSteamFarm.Helpers;
|
||||||
|
using ArchiSteamFarm.Helpers.Json;
|
||||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||||
using ArchiSteamFarm.Web.Responses;
|
using ArchiSteamFarm.Web.Responses;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||||
|
|
||||||
internal sealed class GlobalCache : SerializableFile {
|
internal sealed class GlobalCache : SerializableFile {
|
||||||
internal static readonly ArchiCacheable<ImmutableHashSet<uint>> KnownDepotIDs = new(ResolveKnownDepotIDs, TimeSpan.FromDays(7));
|
internal static readonly ArchiCacheable<FrozenSet<uint>> KnownDepotIDs = new(ResolveKnownDepotIDs, TimeSpan.FromDays(7));
|
||||||
|
|
||||||
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, $"{nameof(SteamTokenDumper)}.cache");
|
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, $"{nameof(SteamTokenDumper)}.cache");
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonInclude]
|
||||||
private readonly ConcurrentDictionary<uint, uint> AppChangeNumbers = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, ulong> AppTokens = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, string> DepotKeys = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, ulong> PackageTokens = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, ulong> SubmittedApps = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, string> SubmittedDepots = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
private readonly ConcurrentDictionary<uint, ulong> SubmittedPackages = new();
|
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
|
||||||
internal uint LastChangeNumber { get; private set; }
|
internal uint LastChangeNumber { get; private set; }
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, uint> AppChangeNumbers { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, ulong> AppTokens { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, string> DepotKeys { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, ulong> SubmittedApps { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, string> SubmittedDepots { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
|
private ConcurrentDictionary<uint, ulong> SubmittedPackages { get; init; } = new();
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
internal GlobalCache() => FilePath = SharedFilePath;
|
internal GlobalCache() => FilePath = SharedFilePath;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
@@ -81,9 +86,6 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public bool ShouldSerializeLastChangeNumber() => LastChangeNumber > 0;
|
public bool ShouldSerializeLastChangeNumber() => LastChangeNumber > 0;
|
||||||
|
|
||||||
[UsedImplicitly]
|
|
||||||
public bool ShouldSerializePackageTokens() => !PackageTokens.IsEmpty;
|
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public bool ShouldSerializeSubmittedApps() => !SubmittedApps.IsEmpty;
|
public bool ShouldSerializeSubmittedApps() => !SubmittedApps.IsEmpty;
|
||||||
|
|
||||||
@@ -93,11 +95,20 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public bool ShouldSerializeSubmittedPackages() => !SubmittedPackages.IsEmpty;
|
public bool ShouldSerializeSubmittedPackages() => !SubmittedPackages.IsEmpty;
|
||||||
|
|
||||||
|
protected override Task Save() => Save(this);
|
||||||
|
|
||||||
internal ulong GetAppToken(uint appID) => AppTokens[appID];
|
internal ulong GetAppToken(uint appID) => AppTokens[appID];
|
||||||
|
|
||||||
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) == false) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value);
|
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) != true) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value);
|
||||||
internal Dictionary<uint, string> GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) == false) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(static depotKey => depotKey.Key, static depotKey => depotKey.Value);
|
internal Dictionary<uint, string> GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) != true) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(static depotKey => depotKey.Key, static depotKey => depotKey.Value);
|
||||||
internal Dictionary<uint, ulong> GetPackageTokensForSubmission() => PackageTokens.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) == false) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(static packageToken => packageToken.Key, static packageToken => packageToken.Value);
|
|
||||||
|
internal Dictionary<uint, ulong> GetPackageTokensForSubmission() {
|
||||||
|
if (ASF.GlobalDatabase == null) {
|
||||||
|
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ASF.GlobalDatabase.PackageAccessTokensReadOnly.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) != true) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(static packageToken => packageToken.Key, static packageToken => packageToken.Value);
|
||||||
|
}
|
||||||
|
|
||||||
internal static async Task<GlobalCache?> Load() {
|
internal static async Task<GlobalCache?> Load() {
|
||||||
if (!File.Exists(SharedFilePath)) {
|
if (!File.Exists(SharedFilePath)) {
|
||||||
@@ -117,7 +128,7 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalCache = JsonConvert.DeserializeObject<GlobalCache>(json);
|
globalCache = json.ToJsonObject<GlobalCache>();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ASF.ArchiLogger.LogGenericException(e);
|
ASF.ArchiLogger.LogGenericException(e);
|
||||||
|
|
||||||
@@ -180,7 +191,6 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
if (clear) {
|
if (clear) {
|
||||||
AppTokens.Clear();
|
AppTokens.Clear();
|
||||||
DepotKeys.Clear();
|
DepotKeys.Clear();
|
||||||
PackageTokens.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Utilities.InBackground(Save);
|
Utilities.InBackground(Save);
|
||||||
@@ -261,25 +271,6 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
Utilities.InBackground(Save);
|
Utilities.InBackground(Save);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UpdatePackageTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> packageTokens) {
|
|
||||||
ArgumentNullException.ThrowIfNull(packageTokens);
|
|
||||||
|
|
||||||
bool save = false;
|
|
||||||
|
|
||||||
foreach ((uint packageID, ulong packageToken) in packageTokens) {
|
|
||||||
if (PackageTokens.TryGetValue(packageID, out ulong previousPackageToken) && (previousPackageToken == packageToken)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
PackageTokens[packageID] = packageToken;
|
|
||||||
save = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (save) {
|
|
||||||
Utilities.InBackground(Save);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateSubmittedData(IReadOnlyDictionary<uint, ulong> apps, IReadOnlyDictionary<uint, ulong> packages, IReadOnlyDictionary<uint, string> depots) {
|
internal void UpdateSubmittedData(IReadOnlyDictionary<uint, ulong> apps, IReadOnlyDictionary<uint, ulong> packages, IReadOnlyDictionary<uint, string> depots) {
|
||||||
ArgumentNullException.ThrowIfNull(apps);
|
ArgumentNullException.ThrowIfNull(apps);
|
||||||
ArgumentNullException.ThrowIfNull(packages);
|
ArgumentNullException.ThrowIfNull(packages);
|
||||||
@@ -325,7 +316,7 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
return (depotKey.Length == 64) && Utilities.IsValidHexadecimalText(depotKey);
|
return (depotKey.Length == 64) && Utilities.IsValidHexadecimalText(depotKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<(bool Success, ImmutableHashSet<uint>? Result)> ResolveKnownDepotIDs(CancellationToken cancellationToken = default) {
|
private static async Task<(bool Success, FrozenSet<uint>? Result)> ResolveKnownDepotIDs(CancellationToken cancellationToken = default) {
|
||||||
if (ASF.WebBrowser == null) {
|
if (ASF.WebBrowser == null) {
|
||||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||||
}
|
}
|
||||||
@@ -362,7 +353,7 @@ internal sealed class GlobalCache : SerializableFile {
|
|||||||
result.Add(depotID);
|
result.Add(depotID);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result.Count > 0, result.ToImmutableHashSet());
|
return (result.Count > 0, result.ToFrozenSet());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ASF.ArchiLogger.LogGenericWarningException(e);
|
ASF.ArchiLogger.LogGenericWarningException(e);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -19,16 +19,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||||
|
|
||||||
public sealed class GlobalConfigExtension {
|
public sealed class GlobalConfigExtension {
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private set; }
|
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private init; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonInclude]
|
||||||
public bool SteamTokenDumperPluginEnabled { get; private set; }
|
public bool SteamTokenDumperPluginEnabled { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal GlobalConfigExtension() { }
|
internal GlobalConfigExtension() { }
|
||||||
|
|||||||
@@ -62,6 +62,14 @@
|
|||||||
PublicKeyToken=b77a5c561934e089
|
PublicKeyToken=b77a5c561934e089
|
||||||
</value>
|
</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||||
|
<value>{0} je onemogućen zbog nedostajućih tokena za izgradnju</value>
|
||||||
|
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||||
|
</data>
|
||||||
|
<data name="PluginDisabledInConfig" xml:space="preserve">
|
||||||
|
<value>{0} je trenutno onemogućen po vašoj konfiguraciji. Ako želite pomoći SteamDB u dostavljanju podataka, molimo pogledajte naš wiki.</value>
|
||||||
|
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -70,9 +78,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||||
|
<value>Preuzimanje informacija o {0} aplikaciji...</value>
|
||||||
|
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,11 @@
|
|||||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="PluginDisabledInConfig" xml:space="preserve">
|
<data name="PluginDisabledInConfig" xml:space="preserve">
|
||||||
<value>{0} ist gemäß ihrer Konfiguration derzeit deaktiviert. Wenn Sie SteamDB bei der Daten-Sammlung helfen möchten, sehen Sie sich bitte unser Wiki an.</value>
|
<value>{0} ist gemäß Ihrer Konfiguration derzeit deaktiviert. Wenn Sie SteamDB bei der Daten-Sammlung helfen möchten, sehen Sie sich bitte unser Wiki an.</value>
|
||||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="PluginInitializedAndEnabled" xml:space="preserve">
|
<data name="PluginInitializedAndEnabled" xml:space="preserve">
|
||||||
<value>{0} wurde erfolgreich initialisiert. Wir danken Ihnen im Voraus für ihre Hilfe. Die erste Übermittlung wird in etwa {1} ab jetzt erfolgen.</value>
|
<value>{0} wurde erfolgreich initialisiert. Wir danken Ihnen im Voraus für Ihre Hilfe. Die erste Übermittlung wird in etwa {1} ab jetzt erfolgen.</value>
|
||||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
|
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
|
||||||
|
|||||||
@@ -97,7 +97,10 @@
|
|||||||
<value>Pabeidza iegūt kopumā {0} aplikāciju piekļuves marķierus.</value>
|
<value>Pabeidza iegūt kopumā {0} aplikāciju piekļuves marķierus.</value>
|
||||||
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
|
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BotRetrievingTotalDepots" xml:space="preserve">
|
||||||
|
<value>Notiek visu depot atslēgu izgūšana, kopā {0} lietotnēm...</value>
|
||||||
|
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||||
|
</data>
|
||||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||||
<value>Iegūst {0} aplikāciju informāciju...</value>
|
<value>Iegūst {0} aplikāciju informāciju...</value>
|
||||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||||
@@ -106,13 +109,33 @@
|
|||||||
<value>Pabeidza iegūt {0} aplikāciju informāciju.</value>
|
<value>Pabeidza iegūt {0} aplikāciju informāciju.</value>
|
||||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||||
|
<value>Veiksmīgi izgūtas {0} no {1} depot atslēgām.</value>
|
||||||
|
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
|
||||||
|
</data>
|
||||||
|
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||||
|
<value>Pabeigta visu depot atslēgu izgūšana, kopā {0} lietotnēm.</value>
|
||||||
|
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||||
|
<value>Nav jaunu datu, ko iesniegt, viss ir atjaunināts.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||||
|
<value>Nevarēja iesniegt datus, jo nav derīgas SteamID kopas, ko mēs varētu klasificēt kā atbalstītāju. Apsveriet iespēju iestatīt {0} rekvizītu.</value>
|
||||||
|
<comment>{0} will be replaced by the name of the config property (e.g. "SteamOwnerID") that the user is expected to set</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionInProgress" xml:space="preserve">
|
||||||
|
<value>Iesniedz reģistrētās lietotnes/pakotnes/depot, kopumā: {0}/{1}/{2}...</value>
|
||||||
|
<comment>{0} will be replaced by the number of app access tokens being submitted, {1} will be replaced by the number of package access tokens being submitted, {2} will be replaced by the number of depot keys being submitted</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
|
||||||
|
<value>Iesniegšana neizdevās, jo tika nosūtīti pārāk daudz pieprasījumi. Mēs mēģināsim vēlreiz pēc aptuveni {0} no šī brīža.</value>
|
||||||
|
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||||
|
<value>Dati ir veiksmīgi iesniegti. Serveris ir reģistrējis pavisam jaunas lietotnes/pakotnes/depots: {0} ({1} verificēts)/{2} ({3} verificēts)/{4} ({5} verificēts).</value>
|
||||||
|
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
|
||||||
|
</data>
|
||||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||||
<value>Jaunas aplikācijas: {0}</value>
|
<value>Jaunas aplikācijas: {0}</value>
|
||||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||||
@@ -129,10 +152,25 @@
|
|||||||
<value>Pārbaudītas pakas: {0}</value>
|
<value>Pārbaudītas pakas: {0}</value>
|
||||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
|
||||||
|
<value>Jaunas aplikācijas: {0}</value>
|
||||||
|
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
|
||||||
|
<value>Pārbaudītas aplikācijas: {0}</value>
|
||||||
|
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||||
|
</data>
|
||||||
|
<data name="PluginSecretListInitialized" xml:space="preserve">
|
||||||
|
<value>{0} inicializēts, spraudnis neatrisinās nevienu no šiem: {1}.</value>
|
||||||
|
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
|
||||||
|
</data>
|
||||||
|
<data name="LoadingGlobalCache" xml:space="preserve">
|
||||||
|
<value>Notiek STD globālās kešatmiņas ielāde...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||||
|
<value>Notiek STD globālās kešatmiņas integritātes apstiprināšana...</value>
|
||||||
|
</data>
|
||||||
|
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||||
|
<value>Neizdevās pārbaudīt STD globālās kešatmiņas integritāti. Tas liecina par iespējamu faila/atmiņas bojājumu, tā vietā tiks inicializēta jauna instance.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -109,7 +109,10 @@
|
|||||||
<value>Dokončené získavanie informácií {0} aplikácií.</value>
|
<value>Dokončené získavanie informácií {0} aplikácií.</value>
|
||||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||||
|
<value>Úspešne načítaných {0} z {1} depot kľúčov.</value>
|
||||||
|
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
|
||||||
|
</data>
|
||||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||||
<value>Dokončené získanie všetkých kľúčov položiek {0} aplikácií.</value>
|
<value>Dokončené získanie všetkých kľúčov položiek {0} aplikácií.</value>
|
||||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||||
@@ -161,7 +164,13 @@
|
|||||||
<value>{0} inicializovaných, modul nebude pracovať so žiadnym: {1}.</value>
|
<value>{0} inicializovaných, modul nebude pracovať so žiadnym: {1}.</value>
|
||||||
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
|
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LoadingGlobalCache" xml:space="preserve">
|
||||||
|
<value>Načítanie globálnej vyrovnávacej pamäti STD...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||||
|
<value>Overovanie globálnej vyrovnávacej pamäti STD...</value>
|
||||||
|
</data>
|
||||||
|
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||||
|
<value>Overovanie globálnej vyrovnávacej pamäti STD sa nepodarilo. To naznačuje, že mohlo dôjsť k poškodeniu súboru/pamäti, miesto toho bude inicializovaná nová inštancia.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,29 +20,33 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using ArchiSteamFarm.Helpers.Json;
|
||||||
using ArchiSteamFarm.IPC.Integration;
|
using ArchiSteamFarm.IPC.Integration;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||||
|
|
||||||
public sealed class SteamTokenDumperConfig {
|
public sealed class SteamTokenDumperConfig {
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonInclude]
|
||||||
public bool Enabled { get; internal set; }
|
public bool Enabled { get; internal set; }
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||||
public ImmutableHashSet<uint> SecretAppIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
public ImmutableHashSet<uint> SecretAppIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||||
public ImmutableHashSet<uint> SecretDepotIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
public ImmutableHashSet<uint> SecretDepotIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonDisallowNull]
|
||||||
|
[JsonInclude]
|
||||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
public ImmutableHashSet<uint> SecretPackageIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
[JsonInclude]
|
||||||
public bool SkipAutoGrantPackages { get; private set; } = true;
|
public bool SkipAutoGrantPackages { get; private init; } = true;
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal SteamTokenDumperConfig() { }
|
internal SteamTokenDumperConfig() { }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -30,6 +30,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
|||||||
public sealed class SteamTokenDumperController : ArchiController {
|
public sealed class SteamTokenDumperController : ArchiController {
|
||||||
[HttpGet(nameof(GlobalConfigExtension))]
|
[HttpGet(nameof(GlobalConfigExtension))]
|
||||||
[ProducesResponseType<GlobalConfigExtension>((int) HttpStatusCode.OK)]
|
[ProducesResponseType<GlobalConfigExtension>((int) HttpStatusCode.OK)]
|
||||||
[SwaggerOperation(Tags = new[] { nameof(GlobalConfigExtension) })]
|
[SwaggerOperation(Tags = [nameof(GlobalConfigExtension)])]
|
||||||
public ActionResult<GlobalConfigExtension> Get() => Ok(new GlobalConfigExtension());
|
public ActionResult<GlobalConfigExtension> Get() => Ok(new GlobalConfigExtension());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,27 +21,30 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Frozen;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Core;
|
using ArchiSteamFarm.Core;
|
||||||
using ArchiSteamFarm.Helpers;
|
using ArchiSteamFarm.Helpers;
|
||||||
|
using ArchiSteamFarm.Helpers.Json;
|
||||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||||
using ArchiSteamFarm.Plugins;
|
using ArchiSteamFarm.Plugins;
|
||||||
using ArchiSteamFarm.Plugins.Interfaces;
|
using ArchiSteamFarm.Plugins.Interfaces;
|
||||||
using ArchiSteamFarm.Steam;
|
using ArchiSteamFarm.Steam;
|
||||||
|
using ArchiSteamFarm.Steam.Interaction;
|
||||||
using ArchiSteamFarm.Storage;
|
using ArchiSteamFarm.Storage;
|
||||||
using ArchiSteamFarm.Web;
|
using ArchiSteamFarm.Web;
|
||||||
using ArchiSteamFarm.Web.Responses;
|
using ArchiSteamFarm.Web.Responses;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||||
@@ -50,7 +53,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
|||||||
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges {
|
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges {
|
||||||
private const ushort DepotsRateLimitingDelay = 500;
|
private const ushort DepotsRateLimitingDelay = 500;
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
internal static SteamTokenDumperConfig? Config { get; private set; }
|
internal static SteamTokenDumperConfig? Config { get; private set; }
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<Bot, IDisposable> BotSubscriptions = new();
|
private static readonly ConcurrentDictionary<Bot, IDisposable> BotSubscriptions = new();
|
||||||
@@ -61,15 +63,17 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
private static GlobalCache? GlobalCache;
|
private static GlobalCache? GlobalCache;
|
||||||
private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue;
|
private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue;
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override string Name => nameof(SteamTokenDumperPlugin);
|
public override string Name => nameof(SteamTokenDumperPlugin);
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonInclude]
|
||||||
|
[Required]
|
||||||
public override Version Version => typeof(SteamTokenDumperPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
public override Version Version => typeof(SteamTokenDumperPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||||
|
|
||||||
public Task<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(Config?.Enabled == true ? GlobalCache?.LastChangeNumber ?? 0 : 0);
|
public Task<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(GlobalCache?.LastChangeNumber ?? 0);
|
||||||
|
|
||||||
public async Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
public async Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||||
if (!SharedInfo.HasValidToken) {
|
if (!SharedInfo.HasValidToken) {
|
||||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledMissingBuildToken, nameof(SteamTokenDumperPlugin)));
|
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledMissingBuildToken, nameof(SteamTokenDumperPlugin)));
|
||||||
|
|
||||||
@@ -80,15 +84,19 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
SteamTokenDumperConfig? config = null;
|
SteamTokenDumperConfig? config = null;
|
||||||
|
|
||||||
if (additionalConfigProperties != null) {
|
if (additionalConfigProperties != null) {
|
||||||
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
|
foreach ((string configProperty, JsonElement configValue) in additionalConfigProperties) {
|
||||||
try {
|
try {
|
||||||
switch (configProperty) {
|
switch (configProperty) {
|
||||||
case nameof(GlobalConfigExtension.SteamTokenDumperPlugin):
|
case nameof(GlobalConfigExtension.SteamTokenDumperPlugin):
|
||||||
config = configValue.ToObject<SteamTokenDumperConfig>();
|
config = configValue.ToJsonObject<SteamTokenDumperConfig>();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled):
|
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.False:
|
||||||
isEnabled = configValue.Value<bool>();
|
isEnabled = false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.True:
|
||||||
|
isEnabled = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -101,30 +109,6 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config ??= new SteamTokenDumperConfig();
|
|
||||||
|
|
||||||
if (isEnabled) {
|
|
||||||
config.Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.Enabled) {
|
|
||||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledInConfig, nameof(SteamTokenDumperPlugin)));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.SecretAppIDs.IsEmpty) {
|
|
||||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretAppIDs), string.Join(", ", config.SecretAppIDs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.SecretPackageIDs.IsEmpty) {
|
|
||||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretPackageIDs), string.Join(", ", config.SecretPackageIDs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.SecretDepotIDs.IsEmpty) {
|
|
||||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretDepotIDs), string.Join(", ", config.SecretDepotIDs)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
if (GlobalCache == null) {
|
||||||
GlobalCache? globalCache = await GlobalCache.Load().ConfigureAwait(false);
|
GlobalCache? globalCache = await GlobalCache.Load().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -137,8 +121,40 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isEnabled && (config == null)) {
|
||||||
|
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledInConfig, nameof(SteamTokenDumperPlugin)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config ??= new SteamTokenDumperConfig();
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
config.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.Enabled) {
|
||||||
|
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledInConfig, nameof(SteamTokenDumperPlugin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.SecretAppIDs.IsEmpty) {
|
||||||
|
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretAppIDs), string.Join(", ", config.SecretAppIDs)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.SecretPackageIDs.IsEmpty) {
|
||||||
|
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretPackageIDs), string.Join(", ", config.SecretPackageIDs)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.SecretDepotIDs.IsEmpty) {
|
||||||
|
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginSecretListInitialized, nameof(config.SecretDepotIDs), string.Join(", ", config.SecretDepotIDs)));
|
||||||
|
}
|
||||||
|
|
||||||
Config = config;
|
Config = config;
|
||||||
|
|
||||||
|
if (!config.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning disable CA5394 // This call isn't used in a security-sensitive manner
|
#pragma warning disable CA5394 // This call isn't used in a security-sensitive manner
|
||||||
TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload));
|
TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload));
|
||||||
#pragma warning restore CA5394 // This call isn't used in a security-sensitive manner
|
#pragma warning restore CA5394 // This call isn't used in a security-sensitive manner
|
||||||
@@ -162,29 +178,24 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
throw new ArgumentNullException(nameof(args));
|
throw new ArgumentNullException(nameof(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (args[0].ToUpperInvariant()) {
|
switch (args.Length) {
|
||||||
case "STD" when access >= EAccess.Owner:
|
case 1:
|
||||||
if (Config is not { Enabled: true }) {
|
switch (args[0].ToUpperInvariant()) {
|
||||||
return Task.FromResult((string?) string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(Config)));
|
case "STD":
|
||||||
|
return Task.FromResult(ResponseRefreshManually(access, bot));
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan minimumTimeBetweenUpload = TimeSpan.FromMinutes(SharedInfo.MinimumMinutesBetweenUploads);
|
break;
|
||||||
|
|
||||||
if (LastUploadAt + minimumTimeBetweenUpload > DateTimeOffset.UtcNow) {
|
|
||||||
return Task.FromResult((string?) string.Format(CultureInfo.CurrentCulture, Strings.SubmissionFailedTooManyRequests, minimumTimeBetweenUpload.ToHumanReadable()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
|
||||||
lock (SubmissionSemaphore) {
|
|
||||||
SubmissionTimer.Change(TimeSpan.Zero, TimeSpan.FromHours(SharedInfo.HoursBetweenUploads));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult((string?) ArchiSteamFarm.Localization.Strings.Done);
|
|
||||||
case "STD" when access > EAccess.None:
|
|
||||||
return Task.FromResult((string?) ArchiSteamFarm.Localization.Strings.ErrorAccessDenied);
|
|
||||||
default:
|
default:
|
||||||
return Task.FromResult((string?) null);
|
switch (args[0].ToUpperInvariant()) {
|
||||||
|
case "STD":
|
||||||
|
return Task.FromResult(ResponseRefreshManually(access, Utilities.GetArgsAsText(args, 1, ","), steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.FromResult((string?) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnBotDestroy(Bot bot) {
|
public async Task OnBotDestroy(Bot bot) {
|
||||||
@@ -207,7 +218,8 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
public async Task OnBotInit(Bot bot) {
|
public async Task OnBotInit(Bot bot) {
|
||||||
ArgumentNullException.ThrowIfNull(bot);
|
ArgumentNullException.ThrowIfNull(bot);
|
||||||
|
|
||||||
if (Config is not { Enabled: true }) {
|
if (GlobalCache == null) {
|
||||||
|
// We can't operate like this anyway, skip initialization of synchronization structures
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,15 +267,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
ArgumentNullException.ThrowIfNull(appChanges);
|
ArgumentNullException.ThrowIfNull(appChanges);
|
||||||
ArgumentNullException.ThrowIfNull(packageChanges);
|
ArgumentNullException.ThrowIfNull(packageChanges);
|
||||||
|
|
||||||
if (Config is not { Enabled: true }) {
|
GlobalCache?.OnPICSChanges(currentChangeNumber, appChanges);
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalCache.OnPICSChanges(currentChangeNumber, appChanges);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -271,15 +275,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
public Task OnPICSChangesRestart(uint currentChangeNumber) {
|
public Task OnPICSChangesRestart(uint currentChangeNumber) {
|
||||||
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
||||||
|
|
||||||
if (Config is not { Enabled: true }) {
|
GlobalCache?.OnPICSChangesRestart(currentChangeNumber);
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalCache.OnPICSChangesRestart(currentChangeNumber);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -300,32 +296,34 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
// Schedule a refresh in a while from now
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<uint, ulong> packageTokens = callback.LicenseList.Where(static license => !Config.SecretPackageIDs.Contains(license.PackageID) && ((license.PaymentMethod != EPaymentMethod.AutoGrant) || !Config.SkipAutoGrantPackages)).GroupBy(static license => license.PackageID).ToDictionary(static group => group.Key, static group => group.OrderByDescending(static license => license.TimeCreated).First().AccessToken);
|
if (!await synchronization.RefreshSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||||
|
// Another refresh is in progress, skip the refresh for now
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GlobalCache.UpdatePackageTokens(packageTokens);
|
try {
|
||||||
|
synchronization.RefreshTimer.Change(TimeSpan.FromMinutes(1), TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh));
|
||||||
await Refresh(bot, packageTokens.Keys).ConfigureAwait(false);
|
} finally {
|
||||||
|
synchronization.RefreshSemaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async void OnSubmissionTimer(object? state = null) => await SubmitData().ConfigureAwait(false);
|
private static async void OnSubmissionTimer(object? state = null) => await SubmitData().ConfigureAwait(false);
|
||||||
|
|
||||||
private static async Task Refresh(Bot bot, IReadOnlyCollection<uint>? packageIDs = null) {
|
private static async Task Refresh(Bot bot) {
|
||||||
ArgumentNullException.ThrowIfNull(bot);
|
ArgumentNullException.ThrowIfNull(bot);
|
||||||
|
|
||||||
if (Config is not { Enabled: true }) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
if (GlobalCache == null) {
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
throw new InvalidOperationException(nameof(GlobalCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ASF.GlobalDatabase == null) {
|
if (ASF.GlobalDatabase == null) {
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
|
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
|
||||||
@@ -343,17 +341,17 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
packageIDs ??= bot.OwnedPackageIDs.Where(static package => !Config.SecretPackageIDs.Contains(package.Key) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || !Config.SkipAutoGrantPackages)).Select(static package => package.Key).ToHashSet();
|
HashSet<uint> packageIDs = bot.OwnedPackageIDs.Where(static package => (Config?.SecretPackageIDs.Contains(package.Key) != true) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || (Config?.SkipAutoGrantPackages == false))).Select(static package => package.Key).ToHashSet();
|
||||||
|
|
||||||
HashSet<uint> appIDsToRefresh = new();
|
HashSet<uint> appIDsToRefresh = [];
|
||||||
|
|
||||||
foreach (uint packageID in packageIDs.Where(static packageID => !Config.SecretPackageIDs.Contains(packageID))) {
|
foreach (uint packageID in packageIDs.Where(static packageID => Config?.SecretPackageIDs.Contains(packageID) != true)) {
|
||||||
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out PackageData? packageData) || (packageData.AppIDs == null)) {
|
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out PackageData? packageData) || (packageData.AppIDs == null)) {
|
||||||
// ASF might not have the package info for us at the moment, we'll retry later
|
// ASF might not have the package info for us at the moment, we'll retry later
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
appIDsToRefresh.UnionWith(packageData.AppIDs.Where(static appID => !Config.SecretAppIDs.Contains(appID) && GlobalCache.ShouldRefreshAppInfo(appID)));
|
appIDsToRefresh.UnionWith(packageData.AppIDs.Where(static appID => (Config?.SecretAppIDs.Contains(appID) != true) && GlobalCache.ShouldRefreshAppInfo(appID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appIDsToRefresh.Count == 0) {
|
if (appIDsToRefresh.Count == 0) {
|
||||||
@@ -405,7 +403,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalAppAccessTokens, appIDsToRefresh.Count));
|
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalAppAccessTokens, appIDsToRefresh.Count));
|
||||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotRetrievingTotalDepots, appIDsToRefresh.Count));
|
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotRetrievingTotalDepots, appIDsToRefresh.Count));
|
||||||
|
|
||||||
(_, ImmutableHashSet<uint>? knownDepotIDs) = await GlobalCache.KnownDepotIDs.GetValue(ECacheFallback.SuccessPreviously).ConfigureAwait(false);
|
(_, FrozenSet<uint>? knownDepotIDs) = await GlobalCache.KnownDepotIDs.GetValue(ECacheFallback.SuccessPreviously).ConfigureAwait(false);
|
||||||
|
|
||||||
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -458,7 +456,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
bool shouldFetchMainKey = false;
|
bool shouldFetchMainKey = false;
|
||||||
|
|
||||||
foreach (KeyValue depot in app.KeyValues["depots"].Children) {
|
foreach (KeyValue depot in app.KeyValues["depots"].Children) {
|
||||||
if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || Config.SecretDepotIDs.Contains(depotID) || !GlobalCache.ShouldRefreshDepotKey(depotID)) {
|
if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || (Config?.SecretDepotIDs.Contains(depotID) == true) || !GlobalCache.ShouldRefreshDepotKey(depotID)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +519,9 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, depotKeysSuccessful, depotKeysTotal));
|
if (depotKeysTotal > 0) {
|
||||||
|
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, depotKeysSuccessful, depotKeysTotal));
|
||||||
|
}
|
||||||
|
|
||||||
if (depotKeysSuccessful < depotKeysTotal) {
|
if (depotKeysSuccessful < depotKeysTotal) {
|
||||||
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
|
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
|
||||||
@@ -534,9 +534,11 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
|
|
||||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalDepots, appIDsToRefresh.Count));
|
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalDepots, appIDsToRefresh.Count));
|
||||||
} finally {
|
} finally {
|
||||||
TimeSpan timeSpan = TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh);
|
if (Config?.Enabled == true) {
|
||||||
|
TimeSpan timeSpan = TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh);
|
||||||
|
|
||||||
synchronization.RefreshTimer.Change(timeSpan, timeSpan);
|
synchronization.RefreshTimer.Change(timeSpan, timeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -546,15 +548,74 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? ResponseRefreshManually(EAccess access, Bot bot) {
|
||||||
|
if (!Enum.IsDefined(access)) {
|
||||||
|
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(bot);
|
||||||
|
|
||||||
|
if (access < EAccess.Master) {
|
||||||
|
return access > EAccess.None ? bot.Commands.FormatBotResponse(ArchiSteamFarm.Localization.Strings.ErrorAccessDenied) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GlobalCache == null) {
|
||||||
|
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(GlobalCache)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Utilities.InBackground(
|
||||||
|
async () => {
|
||||||
|
await Refresh(bot).ConfigureAwait(false);
|
||||||
|
await SubmitData().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return bot.Commands.FormatBotResponse(ArchiSteamFarm.Localization.Strings.Done);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResponseRefreshManually(EAccess access, string botNames, ulong steamID = 0) {
|
||||||
|
if (!Enum.IsDefined(access)) {
|
||||||
|
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(botNames);
|
||||||
|
|
||||||
|
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<Bot>? bots = Bot.GetBots(botNames);
|
||||||
|
|
||||||
|
if ((bots == null) || (bots.Count == 0)) {
|
||||||
|
return access >= EAccess.Owner ? Commands.FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.BotNotFound, botNames)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bots.RemoveWhere(bot => Commands.GetProxyAccess(bot, access, steamID) < EAccess.Master) > 0) {
|
||||||
|
if (bots.Count == 0) {
|
||||||
|
return access >= EAccess.Owner ? Commands.FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.BotNotFound, botNames)) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GlobalCache == null) {
|
||||||
|
return Commands.FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(GlobalCache)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Utilities.InBackground(
|
||||||
|
async () => {
|
||||||
|
await Utilities.InParallel(bots.Select(static bot => Refresh(bot))).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await SubmitData().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Commands.FormatStaticResponse(ArchiSteamFarm.Localization.Strings.Done);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task SubmitData(CancellationToken cancellationToken = default) {
|
private static async Task SubmitData(CancellationToken cancellationToken = default) {
|
||||||
if (Bot.Bots == null) {
|
if (Bot.Bots == null) {
|
||||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config is not { Enabled: true }) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GlobalCache == null) {
|
if (GlobalCache == null) {
|
||||||
throw new InvalidOperationException(nameof(GlobalCache));
|
throw new InvalidOperationException(nameof(GlobalCache));
|
||||||
}
|
}
|
||||||
@@ -610,7 +671,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.StatusCode));
|
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.StatusCode));
|
||||||
|
|
||||||
switch (response.StatusCode) {
|
switch (response.StatusCode) {
|
||||||
case HttpStatusCode.Forbidden:
|
case HttpStatusCode.Forbidden when Config?.Enabled == true:
|
||||||
// SteamDB told us to stop submitting data for now
|
// SteamDB told us to stop submitting data for now
|
||||||
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
||||||
lock (SubmissionSemaphore) {
|
lock (SubmissionSemaphore) {
|
||||||
@@ -623,7 +684,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
GlobalCache.Reset(true);
|
GlobalCache.Reset(true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case HttpStatusCode.TooManyRequests:
|
case HttpStatusCode.TooManyRequests when Config?.Enabled == true:
|
||||||
// SteamDB told us to try again later
|
// SteamDB told us to try again later
|
||||||
#pragma warning disable CA5394 // This call isn't used in a security-sensitive manner
|
#pragma warning disable CA5394 // This call isn't used in a security-sensitive manner
|
||||||
TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload));
|
TimeSpan startIn = TimeSpan.FromMinutes(Random.Shared.Next(SharedInfo.MinimumMinutesBeforeFirstUpload, SharedInfo.MaximumMinutesBeforeFirstUpload));
|
||||||
@@ -649,7 +710,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.Content.Data == null) {
|
if (response.Content.Data == null) {
|
||||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid), nameof(response.Content.Data));
|
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid, nameof(response.Content.Data)));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
<PackageReference Include="MSTest" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" />
|
|
||||||
<PackageReference Include="MSTest.TestFramework" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -39,7 +39,7 @@ public sealed class Bot {
|
|||||||
{ 43, MinCardsPerBadge + 1 }
|
{ 43, MinCardsPerBadge + 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
HashSet<Asset> items = new();
|
HashSet<Asset> items = [];
|
||||||
|
|
||||||
foreach ((uint appID, byte cards) in itemsPerSet) {
|
foreach ((uint appID, byte cards) in itemsPerSet) {
|
||||||
for (byte i = 1; i <= cards; i++) {
|
for (byte i = 1; i <= cards; i++) {
|
||||||
@@ -55,30 +55,27 @@ public sealed class Bot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
|
||||||
public void MaxItemsTooSmall() {
|
public void MaxItemsTooSmall() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(2, appID)
|
CreateCard(2, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1);
|
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MoreCardsThanNeeded() {
|
public void MoreCardsThanNeeded() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(2, appID),
|
CreateCard(2, appID),
|
||||||
CreateCard(3, appID)
|
CreateCard(3, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||||
|
|
||||||
@@ -95,12 +92,12 @@ public sealed class Bot {
|
|||||||
public void MultipleSets() {
|
public void MultipleSets() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(2, appID),
|
CreateCard(2, appID),
|
||||||
CreateCard(2, appID)
|
CreateCard(2, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||||
|
|
||||||
@@ -116,11 +113,11 @@ public sealed class Bot {
|
|||||||
public void MultipleSetsDifferentAmount() {
|
public void MultipleSetsDifferentAmount() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, 2),
|
CreateCard(1, appID, 2),
|
||||||
CreateCard(2, appID),
|
CreateCard(2, appID),
|
||||||
CreateCard(2, appID)
|
CreateCard(2, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||||
|
|
||||||
@@ -136,7 +133,7 @@ public sealed class Bot {
|
|||||||
public void MutliRarityAndType() {
|
public void MutliRarityAndType() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
||||||
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
||||||
|
|
||||||
@@ -158,7 +155,7 @@ public sealed class Bot {
|
|||||||
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
||||||
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
||||||
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
|
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||||
|
|
||||||
@@ -177,10 +174,10 @@ public sealed class Bot {
|
|||||||
public void NotAllCardsPresent() {
|
public void NotAllCardsPresent() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(2, appID)
|
CreateCard(2, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||||
|
|
||||||
@@ -192,10 +189,10 @@ public sealed class Bot {
|
|||||||
public void OneSet() {
|
public void OneSet() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID),
|
CreateCard(1, appID),
|
||||||
CreateCard(2, appID)
|
CreateCard(2, appID)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||||
|
|
||||||
@@ -212,10 +209,10 @@ public sealed class Bot {
|
|||||||
const uint appID0 = 42;
|
const uint appID0 = 42;
|
||||||
const uint appID1 = 43;
|
const uint appID1 = 43;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID0),
|
CreateCard(1, appID0),
|
||||||
CreateCard(1, appID1)
|
CreateCard(1, appID1)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||||
items, new Dictionary<uint, byte> {
|
items, new Dictionary<uint, byte> {
|
||||||
@@ -237,10 +234,10 @@ public sealed class Bot {
|
|||||||
const uint appID0 = 42;
|
const uint appID0 = 42;
|
||||||
const uint appID1 = 43;
|
const uint appID1 = 43;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID0),
|
CreateCard(1, appID0),
|
||||||
CreateCard(1, appID1)
|
CreateCard(1, appID1)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||||
items, new Dictionary<uint, byte> {
|
items, new Dictionary<uint, byte> {
|
||||||
@@ -260,14 +257,14 @@ public sealed class Bot {
|
|||||||
const uint appID1 = 43;
|
const uint appID1 = 43;
|
||||||
const uint appID2 = 44;
|
const uint appID2 = 44;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID0),
|
CreateCard(1, appID0),
|
||||||
CreateCard(2, appID0),
|
CreateCard(2, appID0),
|
||||||
|
|
||||||
CreateCard(1, appID1),
|
CreateCard(1, appID1),
|
||||||
CreateCard(2, appID1),
|
CreateCard(2, appID1),
|
||||||
CreateCard(3, appID1)
|
CreateCard(3, appID1)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||||
items, new Dictionary<uint, byte> {
|
items, new Dictionary<uint, byte> {
|
||||||
@@ -290,10 +287,10 @@ public sealed class Bot {
|
|||||||
public void OtherRarityFullSets() {
|
public void OtherRarityFullSets() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||||
|
|
||||||
@@ -308,10 +305,10 @@ public sealed class Bot {
|
|||||||
public void OtherRarityNoSets() {
|
public void OtherRarityNoSets() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||||
|
|
||||||
@@ -324,13 +321,13 @@ public sealed class Bot {
|
|||||||
public void OtherRarityOneSet() {
|
public void OtherRarityOneSet() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||||
CreateCard(2, appID, rarity: Asset.ERarity.Common),
|
CreateCard(2, appID, rarity: Asset.ERarity.Common),
|
||||||
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
|
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
|
||||||
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
|
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
|
||||||
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
|
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||||
|
|
||||||
@@ -347,10 +344,10 @@ public sealed class Bot {
|
|||||||
public void OtherTypeFullSets() {
|
public void OtherTypeFullSets() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||||
|
|
||||||
@@ -365,10 +362,10 @@ public sealed class Bot {
|
|||||||
public void OtherTypeNoSets() {
|
public void OtherTypeNoSets() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||||
|
|
||||||
@@ -381,13 +378,13 @@ public sealed class Bot {
|
|||||||
public void OtherTypeOneSet() {
|
public void OtherTypeOneSet() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||||
CreateCard(2, appID, type: Asset.EType.TradingCard),
|
CreateCard(2, appID, type: Asset.EType.TradingCard),
|
||||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
|
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
|
||||||
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
|
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
|
||||||
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
|
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||||
|
|
||||||
@@ -404,10 +401,10 @@ public sealed class Bot {
|
|||||||
public void TooHighAmount() {
|
public void TooHighAmount() {
|
||||||
const uint appID0 = 42;
|
const uint appID0 = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID0, 2),
|
CreateCard(1, appID0, 2),
|
||||||
CreateCard(2, appID0)
|
CreateCard(2, appID0)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
|
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
|
||||||
|
|
||||||
@@ -423,7 +420,7 @@ public sealed class Bot {
|
|||||||
public void TooManyCardsForSingleTrade() {
|
public void TooManyCardsForSingleTrade() {
|
||||||
const uint appID = 42;
|
const uint appID = 42;
|
||||||
|
|
||||||
HashSet<Asset> items = new();
|
HashSet<Asset> items = [];
|
||||||
|
|
||||||
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
|
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
|
||||||
items.Add(CreateCard(1, appID));
|
items.Add(CreateCard(1, appID));
|
||||||
@@ -440,7 +437,7 @@ public sealed class Bot {
|
|||||||
const uint appID0 = 42;
|
const uint appID0 = 42;
|
||||||
const uint appID1 = 43;
|
const uint appID1 = 43;
|
||||||
|
|
||||||
HashSet<Asset> items = new();
|
HashSet<Asset> items = [];
|
||||||
|
|
||||||
for (byte i = 0; i < 100; i++) {
|
for (byte i = 0; i < 100; i++) {
|
||||||
items.Add(CreateCard(1, appID0));
|
items.Add(CreateCard(1, appID0));
|
||||||
@@ -460,28 +457,27 @@ public sealed class Bot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(InvalidOperationException))]
|
|
||||||
public void TooManyCardsPerSet() {
|
public void TooManyCardsPerSet() {
|
||||||
const uint appID0 = 42;
|
const uint appID0 = 42;
|
||||||
const uint appID1 = 43;
|
const uint appID1 = 43;
|
||||||
const uint appID2 = 44;
|
const uint appID2 = 44;
|
||||||
|
|
||||||
HashSet<Asset> items = new() {
|
HashSet<Asset> items = [
|
||||||
CreateCard(1, appID0),
|
CreateCard(1, appID0),
|
||||||
CreateCard(2, appID0),
|
CreateCard(2, appID0),
|
||||||
CreateCard(3, appID0),
|
CreateCard(3, appID0),
|
||||||
CreateCard(4, appID0)
|
CreateCard(4, appID0)
|
||||||
};
|
];
|
||||||
|
|
||||||
GetItemsForFullBadge(
|
Assert.ThrowsException<InvalidOperationException>(
|
||||||
items, new Dictionary<uint, byte> {
|
() => GetItemsForFullBadge(
|
||||||
{ appID0, 3 },
|
items, new Dictionary<uint, byte> {
|
||||||
{ appID1, 3 },
|
{ appID0, 3 },
|
||||||
{ appID2, 3 }
|
{ appID1, 3 },
|
||||||
}
|
{ appID2, 3 }
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection<Asset> itemsToSend) {
|
private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection<Asset> itemsToSend) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -200,60 +200,62 @@ public sealed class SteamChatMessage {
|
|||||||
public async Task RyzhehvostInitialTestForSplitting() {
|
public async Task RyzhehvostInitialTestForSplitting() {
|
||||||
const string prefix = "/me ";
|
const string prefix = "/me ";
|
||||||
|
|
||||||
const string message = @"<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
|
const string message = """
|
||||||
<XLimited5> Уже имеет: app/349520 | Armillo
|
<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
|
||||||
<XLimited5> Уже имеет: app/346330 | BrainBread 2
|
<XLimited5> Уже имеет: app/349520 | Armillo
|
||||||
<XLimited5> Уже имеет: app/1086690 | C-War 2
|
<XLimited5> Уже имеет: app/346330 | BrainBread 2
|
||||||
<XLimited5> Уже имеет: app/730 | Counter-Strike: Global Offensive
|
<XLimited5> Уже имеет: app/1086690 | C-War 2
|
||||||
<XLimited5> Уже имеет: app/838380 | DEAD OR ALIVE 6
|
<XLimited5> Уже имеет: app/730 | Counter-Strike: Global Offensive
|
||||||
<XLimited5> Уже имеет: app/582890 | Estranged: The Departure
|
<XLimited5> Уже имеет: app/838380 | DEAD OR ALIVE 6
|
||||||
<XLimited5> Уже имеет: app/331470 | Everlasting Summer
|
<XLimited5> Уже имеет: app/582890 | Estranged: The Departure
|
||||||
<XLimited5> Уже имеет: app/1078000 | Gamecraft
|
<XLimited5> Уже имеет: app/331470 | Everlasting Summer
|
||||||
<XLimited5> Уже имеет: app/266310 | GameGuru
|
<XLimited5> Уже имеет: app/1078000 | Gamecraft
|
||||||
<XLimited5> Уже имеет: app/275390 | Guacamelee! Super Turbo Championship Edition
|
<XLimited5> Уже имеет: app/266310 | GameGuru
|
||||||
<XLimited5> Уже имеет: app/627690 | Idle Champions of the Forgotten Realms
|
<XLimited5> Уже имеет: app/275390 | Guacamelee! Super Turbo Championship Edition
|
||||||
<XLimited5> Уже имеет: app/1048540 | Kao the Kangaroo: Round 2
|
<XLimited5> Уже имеет: app/627690 | Idle Champions of the Forgotten Realms
|
||||||
<XLimited5> Уже имеет: app/370910 | Kathy Rain
|
<XLimited5> Уже имеет: app/1048540 | Kao the Kangaroo: Round 2
|
||||||
<XLimited5> Уже имеет: app/343710 | KHOLAT
|
<XLimited5> Уже имеет: app/370910 | Kathy Rain
|
||||||
<XLimited5> Уже имеет: app/253900 | Knights and Merchants
|
<XLimited5> Уже имеет: app/343710 | KHOLAT
|
||||||
<XLimited5> Уже имеет: app/224260 | No More Room in Hell
|
<XLimited5> Уже имеет: app/253900 | Knights and Merchants
|
||||||
<XLimited5> Уже имеет: app/343360 | Particula
|
<XLimited5> Уже имеет: app/224260 | No More Room in Hell
|
||||||
<XLimited5> Уже имеет: app/237870 | Planet Explorers
|
<XLimited5> Уже имеет: app/343360 | Particula
|
||||||
<XLimited5> Уже имеет: app/684680 | Polygoneer
|
<XLimited5> Уже имеет: app/237870 | Planet Explorers
|
||||||
<XLimited5> Уже имеет: app/1089130 | Quake II RTX
|
<XLimited5> Уже имеет: app/684680 | Polygoneer
|
||||||
<XLimited5> Уже имеет: app/755790 | Ring of Elysium
|
<XLimited5> Уже имеет: app/1089130 | Quake II RTX
|
||||||
<XLimited5> Уже имеет: app/1258080 | Shop Titans
|
<XLimited5> Уже имеет: app/755790 | Ring of Elysium
|
||||||
<XLimited5> Уже имеет: app/759530 | Struckd - 3D Game Creator
|
<XLimited5> Уже имеет: app/1258080 | Shop Titans
|
||||||
<XLimited5> Уже имеет: app/269710 | Tumblestone
|
<XLimited5> Уже имеет: app/759530 | Struckd - 3D Game Creator
|
||||||
<XLimited5> Уже имеет: app/304930 | Unturned
|
<XLimited5> Уже имеет: app/269710 | Tumblestone
|
||||||
<XLimited5> Уже имеет: app/1019250 | WWII TCG - World War 2: The Card Game
|
<XLimited5> Уже имеет: app/304930 | Unturned
|
||||||
|
<XLimited5> Уже имеет: app/1019250 | WWII TCG - World War 2: The Card Game
|
||||||
|
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1493800 | Aircraft Carrier Survival: Prolouge.
|
<ASF> 1/1 ботов уже имеют игру app/1493800 | Aircraft Carrier Survival: Prolouge.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/349520 | Armillo.
|
<ASF> 1/1 ботов уже имеют игру app/349520 | Armillo.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/346330 | BrainBread 2.
|
<ASF> 1/1 ботов уже имеют игру app/346330 | BrainBread 2.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1086690 | C-War 2.
|
<ASF> 1/1 ботов уже имеют игру app/1086690 | C-War 2.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/730 | Counter-Strike: Global Offensive.
|
<ASF> 1/1 ботов уже имеют игру app/730 | Counter-Strike: Global Offensive.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/838380 | DEAD OR ALIVE 6.
|
<ASF> 1/1 ботов уже имеют игру app/838380 | DEAD OR ALIVE 6.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/582890 | Estranged: The Departure.
|
<ASF> 1/1 ботов уже имеют игру app/582890 | Estranged: The Departure.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/331470 | Everlasting Summer.
|
<ASF> 1/1 ботов уже имеют игру app/331470 | Everlasting Summer.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1078000 | Gamecraft.
|
<ASF> 1/1 ботов уже имеют игру app/1078000 | Gamecraft.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/266310 | GameGuru.
|
<ASF> 1/1 ботов уже имеют игру app/266310 | GameGuru.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/275390 | Guacamelee! Super Turbo Championship Edition.
|
<ASF> 1/1 ботов уже имеют игру app/275390 | Guacamelee! Super Turbo Championship Edition.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/627690 | Idle Champions of the Forgotten Realms.
|
<ASF> 1/1 ботов уже имеют игру app/627690 | Idle Champions of the Forgotten Realms.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1048540 | Kao the Kangaroo: Round 2.
|
<ASF> 1/1 ботов уже имеют игру app/1048540 | Kao the Kangaroo: Round 2.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/370910 | Kathy Rain.
|
<ASF> 1/1 ботов уже имеют игру app/370910 | Kathy Rain.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/343710 | KHOLAT.
|
<ASF> 1/1 ботов уже имеют игру app/343710 | KHOLAT.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/253900 | Knights and Merchants.
|
<ASF> 1/1 ботов уже имеют игру app/253900 | Knights and Merchants.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/224260 | No More Room in Hell.
|
<ASF> 1/1 ботов уже имеют игру app/224260 | No More Room in Hell.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/343360 | Particula.
|
<ASF> 1/1 ботов уже имеют игру app/343360 | Particula.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/237870 | Planet Explorers.
|
<ASF> 1/1 ботов уже имеют игру app/237870 | Planet Explorers.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/684680 | Polygoneer.
|
<ASF> 1/1 ботов уже имеют игру app/684680 | Polygoneer.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1089130 | Quake II RTX.
|
<ASF> 1/1 ботов уже имеют игру app/1089130 | Quake II RTX.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/755790 | Ring of Elysium.
|
<ASF> 1/1 ботов уже имеют игру app/755790 | Ring of Elysium.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/1258080 | Shop Titans.
|
<ASF> 1/1 ботов уже имеют игру app/1258080 | Shop Titans.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/759530 | Struckd - 3D Game Creator.
|
<ASF> 1/1 ботов уже имеют игру app/759530 | Struckd - 3D Game Creator.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
|
<ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
|
||||||
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.";
|
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.
|
||||||
|
""";
|
||||||
|
|
||||||
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -309,27 +311,21 @@ public sealed class SteamChatMessage {
|
|||||||
Assert.AreEqual(newlinePart, output[3]);
|
Assert.AreEqual(newlinePart, output[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task ThrowsOnTooLongNewlinesPrefix() {
|
public async Task ThrowsOnTooLongNewlinesPrefix() {
|
||||||
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
|
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
|
||||||
|
|
||||||
const string message = "asdf";
|
const string message = "asdf";
|
||||||
|
|
||||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task ThrowsOnTooLongPrefix() {
|
public async Task ThrowsOnTooLongPrefix() {
|
||||||
string prefix = new('x', MaxMessagePrefixBytes + 1);
|
string prefix = new('x', MaxMessagePrefixBytes + 1);
|
||||||
|
|
||||||
const string message = "asdf";
|
const string message = "asdf";
|
||||||
|
|
||||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -30,21 +30,21 @@ namespace ArchiSteamFarm.Tests;
|
|||||||
public sealed class Trading {
|
public sealed class Trading {
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ExploitingNewSetsIsFairButNotNeutral() {
|
public void ExploitingNewSetsIsFairButNotNeutral() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 40),
|
CreateItem(1, 40),
|
||||||
CreateItem(2, 10),
|
CreateItem(2, 10),
|
||||||
CreateItem(3, 10)
|
CreateItem(3, 10)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(2, 5),
|
CreateItem(2, 5),
|
||||||
CreateItem(3, 5)
|
CreateItem(3, 5)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(4)
|
CreateItem(4)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -52,45 +52,45 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MismatchRarityIsNotFair() {
|
public void MismatchRarityIsNotFair() {
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, rarity: Asset.ERarity.Rare) };
|
HashSet<Asset> itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MismatchRealAppIDsIsNotFair() {
|
public void MismatchRealAppIDsIsNotFair() {
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, realAppID: 570) };
|
HashSet<Asset> itemsToGive = [CreateItem(1, realAppID: 570)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MismatchTypesIsNotFair() {
|
public void MismatchTypesIsNotFair() {
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, type: Asset.EType.Emoticon) };
|
HashSet<Asset> itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MultiGameMultiTypeBadReject() {
|
public void MultiGameMultiTypeBadReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, 9, 730, Asset.EType.Emoticon),
|
CreateItem(3, 9, 730, Asset.EType.Emoticon),
|
||||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -98,20 +98,20 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MultiGameMultiTypeNeutralAccept() {
|
public void MultiGameMultiTypeNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -119,21 +119,21 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MultiGameSingleTypeBadReject() {
|
public void MultiGameSingleTypeBadReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, realAppID: 730),
|
CreateItem(3, realAppID: 730),
|
||||||
CreateItem(4, realAppID: 730)
|
CreateItem(4, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3, realAppID: 730)
|
CreateItem(3, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(4, realAppID: 730)
|
CreateItem(4, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -141,20 +141,20 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MultiGameSingleTypeNeutralAccept() {
|
public void MultiGameSingleTypeNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 2),
|
CreateItem(1, 2),
|
||||||
CreateItem(3, realAppID: 730)
|
CreateItem(3, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3, realAppID: 730)
|
CreateItem(3, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(4, realAppID: 730)
|
CreateItem(4, realAppID: 730)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -162,21 +162,17 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2, 2),
|
CreateItem(2, 2),
|
||||||
CreateItem(3),
|
CreateItem(3),
|
||||||
CreateItem(4),
|
CreateItem(4),
|
||||||
CreateItem(5)
|
CreateItem(5)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||||
CreateItem(2)
|
|
||||||
};
|
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||||
CreateItem(3)
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -184,18 +180,14 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameDonationAccept() {
|
public void SingleGameDonationAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [CreateItem(1)];
|
||||||
CreateItem(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||||
CreateItem(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(3, type: Asset.EType.SteamGems)
|
CreateItem(3, type: Asset.EType.SteamGems)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -203,21 +195,21 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameMultiTypeBadReject() {
|
public void SingleGameMultiTypeBadReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, 9, type: Asset.EType.Emoticon),
|
CreateItem(3, 9, type: Asset.EType.Emoticon),
|
||||||
CreateItem(4, type: Asset.EType.Emoticon)
|
CreateItem(4, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(4, type: Asset.EType.Emoticon)
|
CreateItem(4, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(3, type: Asset.EType.Emoticon)
|
CreateItem(3, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -225,20 +217,20 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameMultiTypeNeutralAccept() {
|
public void SingleGameMultiTypeNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, type: Asset.EType.Emoticon)
|
CreateItem(3, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3, type: Asset.EType.Emoticon)
|
CreateItem(3, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(4, type: Asset.EType.Emoticon)
|
CreateItem(4, type: Asset.EType.Emoticon)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -246,19 +238,19 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameQuantityBadReject() {
|
public void SingleGameQuantityBadReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(3)
|
CreateItem(3)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(3)
|
CreateItem(3)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(4, 3) };
|
HashSet<Asset> itemsToReceive = [CreateItem(4, 3)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -266,17 +258,17 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameQuantityBadReject2() {
|
public void SingleGameQuantityBadReject2() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2, 2)
|
CreateItem(2, 2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2, 2)
|
CreateItem(2, 2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 3) };
|
HashSet<Asset> itemsToReceive = [CreateItem(3, 3)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -284,17 +276,17 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameQuantityNeutralAccept() {
|
public void SingleGameQuantityNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 2),
|
CreateItem(1, 2),
|
||||||
CreateItem(2)
|
CreateItem(2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2)
|
CreateItem(2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(3, 2)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -302,13 +294,13 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeBadReject() {
|
public void SingleGameSingleTypeBadReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2)
|
CreateItem(2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -316,18 +308,18 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeBadWithOverpayingReject() {
|
public void SingleGameSingleTypeBadWithOverpayingReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 2),
|
CreateItem(1, 2),
|
||||||
CreateItem(2, 2),
|
CreateItem(2, 2),
|
||||||
CreateItem(3, 2)
|
CreateItem(3, 2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3)
|
CreateItem(3)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -335,14 +327,14 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeBigDifferenceAccept() {
|
public void SingleGameSingleTypeBigDifferenceAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2, 5),
|
CreateItem(2, 5),
|
||||||
CreateItem(3)
|
CreateItem(3)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3) };
|
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -350,23 +342,23 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeBigDifferenceReject() {
|
public void SingleGameSingleTypeBigDifferenceReject() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(2, 2),
|
CreateItem(2, 2),
|
||||||
CreateItem(3, 2),
|
CreateItem(3, 2),
|
||||||
CreateItem(4, 3),
|
CreateItem(4, 3),
|
||||||
CreateItem(5, 10)
|
CreateItem(5, 10)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() {
|
HashSet<Asset> itemsToGive = [
|
||||||
CreateItem(2),
|
CreateItem(2),
|
||||||
CreateItem(5)
|
CreateItem(5)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(3),
|
CreateItem(3),
|
||||||
CreateItem(4)
|
CreateItem(4)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -374,9 +366,9 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeGoodAccept() {
|
public void SingleGameSingleTypeGoodAccept() {
|
||||||
HashSet<Asset> inventory = new() { CreateItem(1, 2) };
|
HashSet<Asset> inventory = [CreateItem(1, 2)];
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -384,9 +376,9 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeNeutralAccept() {
|
public void SingleGameSingleTypeNeutralAccept() {
|
||||||
HashSet<Asset> inventory = new() { CreateItem(1) };
|
HashSet<Asset> inventory = [CreateItem(1)];
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -394,17 +386,17 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
|
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 2),
|
CreateItem(1, 2),
|
||||||
CreateItem(2, 2)
|
CreateItem(2, 2)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
|
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(1),
|
CreateItem(1),
|
||||||
CreateItem(3)
|
CreateItem(3)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
@@ -412,23 +404,23 @@ public sealed class Trading {
|
|||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
|
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
|
||||||
HashSet<Asset> inventory = new() {
|
HashSet<Asset> inventory = [
|
||||||
CreateItem(1, 52),
|
CreateItem(1, 52),
|
||||||
CreateItem(2, 73),
|
CreateItem(2, 73),
|
||||||
CreateItem(3, 52),
|
CreateItem(3, 52),
|
||||||
CreateItem(4, 47),
|
CreateItem(4, 47),
|
||||||
CreateItem(5)
|
CreateItem(5)
|
||||||
};
|
];
|
||||||
|
|
||||||
HashSet<Asset> itemsToGive = new() { CreateItem(2, 73) };
|
HashSet<Asset> itemsToGive = [CreateItem(2, 73)];
|
||||||
|
|
||||||
HashSet<Asset> itemsToReceive = new() {
|
HashSet<Asset> itemsToReceive = [
|
||||||
CreateItem(1, 9),
|
CreateItem(1, 9),
|
||||||
CreateItem(3, 9),
|
CreateItem(3, 9),
|
||||||
CreateItem(4, 8),
|
CreateItem(4, 8),
|
||||||
CreateItem(5, 24),
|
CreateItem(5, 24),
|
||||||
CreateItem(6, 23)
|
CreateItem(6, 23)
|
||||||
};
|
];
|
||||||
|
|
||||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -4,6 +4,9 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
|
|
||||||
|
<!-- TODO: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2550 -->
|
||||||
|
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -13,15 +16,13 @@
|
|||||||
<PackageReference Include="Humanizer" />
|
<PackageReference Include="Humanizer" />
|
||||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||||
<PackageReference Include="Markdig.Signed" />
|
<PackageReference Include="Markdig.Signed" />
|
||||||
<PackageReference Include="Newtonsoft.Json" />
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||||
<PackageReference Include="Nito.AsyncEx.Coordination" />
|
<PackageReference Include="Nito.AsyncEx.Coordination" />
|
||||||
<PackageReference Include="NLog.Web.AspNetCore" />
|
<PackageReference Include="NLog.Web.AspNetCore" />
|
||||||
<PackageReference Include="SteamKit2" />
|
<PackageReference Include="SteamKit2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
|
|
||||||
<PackageReference Include="System.Composition" />
|
<PackageReference Include="System.Composition" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
|
||||||
<PackageReference Include="System.Linq.Async" />
|
<PackageReference Include="System.Linq.Async" />
|
||||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||||
<PackageReference Include="zxcvbn-core" />
|
<PackageReference Include="zxcvbn-core" />
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists($([System.IO.Path]::Combine('overlay', 'variant-base', $(ASFVariant.Split('-')[0]))))">
|
<ItemGroup Condition="'$(ASFVariant)' != '' AND Exists($([System.IO.Path]::Combine('overlay', 'variant-base', $(ASFVariant.Split('-')[0]))))">
|
||||||
<Content Include="overlay/variant-base/$(ASFVariant.Split('-')[0])/**/*.*">
|
<Content Include="overlay/variant-base/$(ASFVariant.Split('-')[0])/**/*.*">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists($([System.IO.Path]::Combine('overlay', 'variant-specific', $(ASFVariant))))">
|
<ItemGroup Condition="'$(ASFVariant)' != '' AND Exists($([System.IO.Path]::Combine('overlay', 'variant-specific', $(ASFVariant))))">
|
||||||
<Content Include="overlay/variant-specific/$(ASFVariant)/**/*.*">
|
<Content Include="overlay/variant-specific/$(ASFVariant)/**/*.*">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -24,11 +24,13 @@ using System.Collections;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.Collections;
|
namespace ArchiSteamFarm.Collections;
|
||||||
|
|
||||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where T : notnull {
|
public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : notnull {
|
||||||
|
[PublicAPI]
|
||||||
public event EventHandler? OnModified;
|
public event EventHandler? OnModified;
|
||||||
|
|
||||||
public int Count => BackingCollection.Count;
|
public int Count => BackingCollection.Count;
|
||||||
@@ -36,15 +38,31 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
|
|
||||||
private readonly ConcurrentDictionary<T, bool> BackingCollection;
|
private readonly ConcurrentDictionary<T, bool> BackingCollection;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
|
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
|
||||||
|
|
||||||
|
public ConcurrentHashSet(IEnumerable<T> collection) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
|
||||||
|
BackingCollection = new ConcurrentDictionary<T, bool>(collection.Select(static item => new KeyValuePair<T, bool>(item, true)));
|
||||||
|
}
|
||||||
|
|
||||||
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
|
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
|
||||||
ArgumentNullException.ThrowIfNull(comparer);
|
ArgumentNullException.ThrowIfNull(comparer);
|
||||||
|
|
||||||
BackingCollection = new ConcurrentDictionary<T, bool>(comparer);
|
BackingCollection = new ConcurrentDictionary<T, bool>(comparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
ArgumentNullException.ThrowIfNull(comparer);
|
||||||
|
|
||||||
|
BackingCollection = new ConcurrentDictionary<T, bool>(collection.Select(static item => new KeyValuePair<T, bool>(item, true)), comparer);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Add(T item) {
|
public bool Add(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
if (!BackingCollection.TryAdd(item, true)) {
|
if (!BackingCollection.TryAdd(item, true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,9 +82,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
OnModified?.Invoke(this, EventArgs.Empty);
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(T item) => BackingCollection.ContainsKey(item);
|
public bool Contains(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
|
return BackingCollection.ContainsKey(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex) {
|
||||||
|
ArgumentNullException.ThrowIfNull(array);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||||
|
|
||||||
|
BackingCollection.Keys.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public void ExceptWith(IEnumerable<T> other) {
|
public void ExceptWith(IEnumerable<T> other) {
|
||||||
ArgumentNullException.ThrowIfNull(other);
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
@@ -79,6 +106,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
|
public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
|
||||||
|
|
||||||
public void IntersectWith(IEnumerable<T> other) {
|
public void IntersectWith(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
|
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
|
||||||
@@ -87,36 +116,48 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
|
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
|
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return this.All(otherSet.Contains);
|
return this.All(otherSet.Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return otherSet.All(Contains);
|
return otherSet.All(Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Overlaps(IEnumerable<T> other) {
|
public bool Overlaps(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return otherSet.Any(Contains);
|
return otherSet.Any(Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(T item) {
|
public bool Remove(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
if (!BackingCollection.TryRemove(item, out _)) {
|
if (!BackingCollection.TryRemove(item, out _)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -127,14 +168,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool SetEquals(IEnumerable<T> other) {
|
public bool SetEquals(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
|
|
||||||
return (otherSet.Count == Count) && otherSet.All(Contains);
|
return (otherSet.Count == Count) && otherSet.All(Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||||
HashSet<T> removed = new();
|
HashSet<T> removed = [];
|
||||||
|
|
||||||
foreach (T item in otherSet.Where(Contains)) {
|
foreach (T item in otherSet.Where(Contains)) {
|
||||||
removed.Add(item);
|
removed.Add(item);
|
||||||
@@ -154,12 +199,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICollection<T>.Add(T item) => Add(item);
|
void ICollection<T>.Add(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
|
Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool AddRange(IEnumerable<T> items) {
|
public bool AddRange(IEnumerable<T> items) {
|
||||||
|
ArgumentNullException.ThrowIfNull(items);
|
||||||
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
foreach (T _ in items.Where(Add)) {
|
foreach (T _ in items.Where(Add)) {
|
||||||
@@ -171,6 +222,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool RemoveRange(IEnumerable<T> items) {
|
public bool RemoveRange(IEnumerable<T> items) {
|
||||||
|
ArgumentNullException.ThrowIfNull(items);
|
||||||
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
foreach (T _ in items.Where(Remove)) {
|
foreach (T _ in items.Where(Remove)) {
|
||||||
@@ -180,8 +233,17 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PublicAPI]
|
||||||
|
public int RemoveWhere(Predicate<T> match) {
|
||||||
|
ArgumentNullException.ThrowIfNull(match);
|
||||||
|
|
||||||
|
return BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
|
||||||
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
|
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
if (SetEquals(other)) {
|
if (SetEquals(other)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -193,6 +255,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
|||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public void ReplaceWith(IEnumerable<T> other) {
|
public void ReplaceWith(IEnumerable<T> other) {
|
||||||
|
ArgumentNullException.ThrowIfNull(other);
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
UnionWith(other);
|
UnionWith(other);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -19,16 +19,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Nito.AsyncEx;
|
using Nito.AsyncEx;
|
||||||
|
|
||||||
namespace ArchiSteamFarm.Collections;
|
namespace ArchiSteamFarm.Collections;
|
||||||
|
|
||||||
internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> where T : notnull {
|
||||||
public bool IsReadOnly => false;
|
[PublicAPI]
|
||||||
|
public event EventHandler? OnModified;
|
||||||
|
|
||||||
internal int Count {
|
[PublicAPI]
|
||||||
|
public int Count {
|
||||||
get {
|
get {
|
||||||
using (Lock.ReaderLock()) {
|
using (Lock.ReaderLock()) {
|
||||||
return BackingCollection.Count;
|
return BackingCollection.Count;
|
||||||
@@ -36,7 +41,9 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<T> BackingCollection = new();
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
private readonly List<T> BackingCollection;
|
||||||
private readonly AsyncReaderWriterLock Lock = new();
|
private readonly AsyncReaderWriterLock Lock = new();
|
||||||
|
|
||||||
int ICollection<T>.Count => Count;
|
int ICollection<T>.Count => Count;
|
||||||
@@ -50,31 +57,55 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set {
|
set {
|
||||||
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection[index] = value;
|
BackingCollection[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public ConcurrentList() => BackingCollection = [];
|
||||||
|
|
||||||
|
public ConcurrentList(IEnumerable<T> collection) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
|
||||||
|
BackingCollection = [..collection];
|
||||||
|
}
|
||||||
|
|
||||||
public void Add(T item) {
|
public void Add(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection.Add(item);
|
BackingCollection.Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear() {
|
public void Clear() {
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection.Clear();
|
BackingCollection.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(T item) {
|
public bool Contains(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
using (Lock.ReaderLock()) {
|
using (Lock.ReaderLock()) {
|
||||||
return BackingCollection.Contains(item);
|
return BackingCollection.Contains(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex) {
|
public void CopyTo(T[] array, int arrayIndex) {
|
||||||
|
ArgumentNullException.ThrowIfNull(array);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||||
|
|
||||||
using (Lock.ReaderLock()) {
|
using (Lock.ReaderLock()) {
|
||||||
BackingCollection.CopyTo(array, arrayIndex);
|
BackingCollection.CopyTo(array, arrayIndex);
|
||||||
}
|
}
|
||||||
@@ -83,35 +114,59 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
|||||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
|
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
|
||||||
|
|
||||||
public int IndexOf(T item) {
|
public int IndexOf(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
using (Lock.ReaderLock()) {
|
using (Lock.ReaderLock()) {
|
||||||
return BackingCollection.IndexOf(item);
|
return BackingCollection.IndexOf(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Insert(int index, T item) {
|
public void Insert(int index, T item) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection.Insert(index, item);
|
BackingCollection.Insert(index, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(T item) {
|
public bool Remove(T item) {
|
||||||
|
ArgumentNullException.ThrowIfNull(item);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
return BackingCollection.Remove(item);
|
if (!BackingCollection.Remove(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAt(int index) {
|
public void RemoveAt(int index) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection.RemoveAt(index);
|
BackingCollection.RemoveAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
internal void ReplaceWith(IEnumerable<T> collection) {
|
[PublicAPI]
|
||||||
|
public void ReplaceWith(IEnumerable<T> collection) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
|
||||||
using (Lock.WriterLock()) {
|
using (Lock.WriterLock()) {
|
||||||
BackingCollection.Clear();
|
BackingCollection.Clear();
|
||||||
BackingCollection.AddRange(collection);
|
BackingCollection.AddRange(collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnModified?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -23,22 +23,17 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ArchiSteamFarm.Core;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.Collections;
|
namespace ArchiSteamFarm.Collections;
|
||||||
|
|
||||||
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> where T : notnull {
|
||||||
private readonly ConcurrentQueue<T> BackingQueue = new();
|
private readonly ConcurrentQueue<T> BackingQueue = new();
|
||||||
|
|
||||||
internal byte MaxCount {
|
internal byte MaxCount {
|
||||||
get => BackingMaxCount;
|
get => BackingMaxCount;
|
||||||
|
|
||||||
set {
|
set {
|
||||||
if (value == 0) {
|
ArgumentOutOfRangeException.ThrowIfZero(value);
|
||||||
ASF.ArchiLogger.LogNullError(value);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BackingMaxCount = value;
|
BackingMaxCount = value;
|
||||||
|
|
||||||
@@ -58,6 +53,8 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
|||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
internal void Enqueue(T obj) {
|
internal void Enqueue(T obj) {
|
||||||
|
ArgumentNullException.ThrowIfNull(obj);
|
||||||
|
|
||||||
BackingQueue.Enqueue(obj);
|
BackingQueue.Enqueue(obj);
|
||||||
|
|
||||||
Resize();
|
Resize();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -23,12 +23,13 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace ArchiSteamFarm.Collections;
|
namespace ArchiSteamFarm.Collections;
|
||||||
|
|
||||||
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
||||||
|
[PublicAPI]
|
||||||
public event EventHandler? OnModified;
|
public event EventHandler? OnModified;
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
@@ -42,8 +43,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public ICollection<TKey> Keys => BackingDictionary.Keys;
|
public ICollection<TKey> Keys => BackingDictionary.Keys;
|
||||||
|
|
||||||
[JsonProperty(Required = Required.DisallowNull)]
|
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary;
|
||||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary = new();
|
|
||||||
|
|
||||||
int ICollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
int ICollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||||
int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||||
@@ -54,7 +54,10 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
|
|
||||||
public TValue this[TKey key] {
|
public TValue this[TKey key] {
|
||||||
get => BackingDictionary[key];
|
get => BackingDictionary[key];
|
||||||
|
|
||||||
set {
|
set {
|
||||||
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
|
|
||||||
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,13 +67,39 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public ObservableConcurrentDictionary() => BackingDictionary = new ConcurrentDictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
|
||||||
|
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer) {
|
||||||
|
ArgumentNullException.ThrowIfNull(comparer);
|
||||||
|
|
||||||
|
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
|
||||||
|
ArgumentNullException.ThrowIfNull(collection);
|
||||||
|
ArgumentNullException.ThrowIfNull(comparer);
|
||||||
|
|
||||||
|
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(collection, comparer);
|
||||||
|
}
|
||||||
|
|
||||||
public void Add(KeyValuePair<TKey, TValue> item) {
|
public void Add(KeyValuePair<TKey, TValue> item) {
|
||||||
(TKey key, TValue value) = item;
|
(TKey key, TValue value) = item;
|
||||||
|
|
||||||
Add(key, value);
|
Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(TKey key, TValue value) => TryAdd(key, value);
|
public void Add(TKey key, TValue value) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
TryAdd(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear() {
|
public void Clear() {
|
||||||
if (BackingDictionary.IsEmpty) {
|
if (BackingDictionary.IsEmpty) {
|
||||||
@@ -82,7 +111,14 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).Contains(item);
|
public bool Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).Contains(item);
|
||||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).CopyTo(array, arrayIndex);
|
|
||||||
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||||
|
ArgumentNullException.ThrowIfNull(array);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||||
|
|
||||||
|
((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => BackingDictionary.GetEnumerator();
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => BackingDictionary.GetEnumerator();
|
||||||
|
|
||||||
public bool Remove(KeyValuePair<TKey, TValue> item) {
|
public bool Remove(KeyValuePair<TKey, TValue> item) {
|
||||||
@@ -98,6 +134,8 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(TKey key) {
|
public bool Remove(TKey key) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
if (!BackingDictionary.TryRemove(key, out _)) {
|
if (!BackingDictionary.TryRemove(key, out _)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -107,14 +145,36 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
|
||||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
return BackingDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
return BackingDictionary.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
|
||||||
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
return BackingDictionary.TryGetValue(key, out value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
return BackingDictionary.TryGetValue(key, out value!);
|
||||||
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool TryAdd(TKey key, TValue value) {
|
public bool TryAdd(TKey key, TValue value) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
if (!BackingDictionary.TryAdd(key, value)) {
|
if (!BackingDictionary.TryAdd(key, value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -125,5 +185,9 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public bool TryGetValue(TKey key, out TValue? value) => BackingDictionary.TryGetValue(key, out value);
|
public bool TryGetValue(TKey key, out TValue? value) {
|
||||||
|
ArgumentNullException.ThrowIfNull(key);
|
||||||
|
|
||||||
|
return BackingDictionary.TryGetValue(key, out value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Frozen;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -30,7 +30,6 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ArchiSteamFarm.Helpers;
|
using ArchiSteamFarm.Helpers;
|
||||||
@@ -76,9 +75,9 @@ public static class ASF {
|
|||||||
internal static ICrossProcessSemaphore? LoginRateLimitingSemaphore { get; private set; }
|
internal static ICrossProcessSemaphore? LoginRateLimitingSemaphore { get; private set; }
|
||||||
internal static ICrossProcessSemaphore? LoginSemaphore { get; private set; }
|
internal static ICrossProcessSemaphore? LoginSemaphore { get; private set; }
|
||||||
internal static ICrossProcessSemaphore? RateLimitingSemaphore { get; private set; }
|
internal static ICrossProcessSemaphore? RateLimitingSemaphore { get; private set; }
|
||||||
internal static ImmutableDictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
|
internal static FrozenDictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
|
||||||
|
|
||||||
private static readonly ImmutableHashSet<string> AssembliesNeededBeforeUpdate = ImmutableHashSet.Create(StringComparer.Ordinal, "System.IO.Pipes");
|
private static readonly FrozenSet<string> AssembliesNeededBeforeUpdate = new HashSet<string>(1, StringComparer.Ordinal) { "System.IO.Pipes" }.ToFrozenSet(StringComparer.Ordinal);
|
||||||
private static readonly SemaphoreSlim UpdateSemaphore = new(1, 1);
|
private static readonly SemaphoreSlim UpdateSemaphore = new(1, 1);
|
||||||
|
|
||||||
private static Timer? AutoUpdatesTimer;
|
private static Timer? AutoUpdatesTimer;
|
||||||
@@ -102,23 +101,32 @@ public static class ASF {
|
|||||||
return fileType switch {
|
return fileType switch {
|
||||||
EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName),
|
EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName),
|
||||||
EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName),
|
EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName),
|
||||||
|
EFileType.Crash => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalCrashFileName),
|
||||||
_ => throw new InvalidOperationException(nameof(fileType))
|
_ => throw new InvalidOperationException(nameof(fileType))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task Init() {
|
internal static async Task<bool> Init() {
|
||||||
if (GlobalConfig == null) {
|
if (GlobalConfig == null) {
|
||||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PluginsCore.InitPlugins()) {
|
|
||||||
await Task.Delay(SharedInfo.InformationDelay).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||||
|
|
||||||
await UpdateAndRestart().ConfigureAwait(false);
|
await UpdateAndRestart().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!Program.IgnoreUnsupportedEnvironment && !await ProtectAgainstCrashes().ConfigureAwait(false)) {
|
||||||
|
ArchiLogger.LogFatalError(Strings.ErrorTooManyCrashes);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.AllowCrashFileRemoval = true;
|
||||||
|
|
||||||
|
if (!await PluginsCore.InitPlugins().ConfigureAwait(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
|
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
|
||||||
await InitRateLimiters().ConfigureAwait(false);
|
await InitRateLimiters().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -143,6 +151,8 @@ public static class ASF {
|
|||||||
if (Program.ConfigWatch) {
|
if (Program.ConfigWatch) {
|
||||||
InitConfigWatchEvents();
|
InitConfigWatchEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsValidBotName(string botName) {
|
internal static bool IsValidBotName(string botName) {
|
||||||
@@ -323,7 +333,7 @@ public static class ASF {
|
|||||||
|
|
||||||
byte[] responseBytes = response.Content as byte[] ?? response.Content.ToArray();
|
byte[] responseBytes = response.Content as byte[] ?? response.Content.ToArray();
|
||||||
|
|
||||||
string checksum = Convert.ToHexString(SHA512.HashData(responseBytes));
|
string checksum = Utilities.GenerateChecksumFor(responseBytes);
|
||||||
|
|
||||||
if (!checksum.Equals(remoteChecksum, StringComparison.OrdinalIgnoreCase)) {
|
if (!checksum.Equals(remoteChecksum, StringComparison.OrdinalIgnoreCase)) {
|
||||||
ArchiLogger.LogGenericError(Strings.ChecksumWrong);
|
ArchiLogger.LogGenericError(Strings.ChecksumWrong);
|
||||||
@@ -442,7 +452,7 @@ public static class ASF {
|
|||||||
{ ArchiWebHandler.SteamHelpURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamHelpURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
{ ArchiWebHandler.SteamHelpURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamHelpURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||||
{ ArchiWebHandler.SteamStoreURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamStoreURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
{ ArchiWebHandler.SteamStoreURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamStoreURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||||
{ WebAPI.DefaultBaseAddress, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(WebAPI)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
{ WebAPI.DefaultBaseAddress, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(WebAPI)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||||
}.ToImmutableDictionary();
|
}.ToFrozenDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LoadAllAssemblies() {
|
private static void LoadAllAssemblies() {
|
||||||
@@ -814,6 +824,37 @@ public static class ASF {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> ProtectAgainstCrashes() {
|
||||||
|
if (Debugging.IsDebugBuild) {
|
||||||
|
// Allow debug builds to run unconditionally, we expect to crash a lot in those
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string crashFilePath = GetFilePath(EFileType.Crash);
|
||||||
|
|
||||||
|
CrashFile crashFile = await CrashFile.CreateOrLoad(crashFilePath).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (crashFile.StartupCount >= WebBrowser.MaxTries) {
|
||||||
|
// We've reached maximum allowed count of recent crashes, return failure
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (now - crashFile.LastStartup > TimeSpan.FromMinutes(5)) {
|
||||||
|
// Last crash was long ago, restart counter
|
||||||
|
crashFile.StartupCount = 1;
|
||||||
|
} else if (++crashFile.StartupCount >= WebBrowser.MaxTries) {
|
||||||
|
// We've reached maximum allowed count of recent crashes, return failure
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
crashFile.LastStartup = now;
|
||||||
|
|
||||||
|
// We're allowing this run to proceed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task RegisterBots() {
|
private static async Task RegisterBots() {
|
||||||
if (GlobalConfig == null) {
|
if (GlobalConfig == null) {
|
||||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||||
@@ -906,6 +947,9 @@ public static class ASF {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow crash file recovery, if needed
|
||||||
|
Program.AllowCrashFileRemoval = true;
|
||||||
|
|
||||||
await RestartOrExit().ConfigureAwait(false);
|
await RestartOrExit().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,6 +1092,7 @@ public static class ASF {
|
|||||||
|
|
||||||
internal enum EFileType : byte {
|
internal enum EFileType : byte {
|
||||||
Config,
|
Config,
|
||||||
Database
|
Database,
|
||||||
|
Crash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -21,14 +21,15 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Frozen;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Resources;
|
using System.Resources;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AngleSharp.Dom;
|
using AngleSharp.Dom;
|
||||||
@@ -38,6 +39,7 @@ using ArchiSteamFarm.Storage;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Humanizer.Localisation;
|
using Humanizer.Localisation;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Microsoft.IdentityModel.JsonWebTokens;
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
using Zxcvbn;
|
using Zxcvbn;
|
||||||
|
|
||||||
@@ -47,9 +49,16 @@ public static class Utilities {
|
|||||||
private const byte TimeoutForLongRunningTasksInSeconds = 60;
|
private const byte TimeoutForLongRunningTasksInSeconds = 60;
|
||||||
|
|
||||||
// normally we'd just use words like "steam" and "farm", but the library we're currently using is a bit iffy about banned words, so we need to also add combinations such as "steamfarm"
|
// normally we'd just use words like "steam" and "farm", but the library we're currently using is a bit iffy about banned words, so we need to also add combinations such as "steamfarm"
|
||||||
private static readonly ImmutableHashSet<string> ForbiddenPasswordPhrases = ImmutableHashSet.Create(StringComparer.InvariantCultureIgnoreCase, "archisteamfarm", "archi", "steam", "farm", "archisteam", "archifarm", "steamfarm", "asf", "asffarm", "password");
|
private static readonly FrozenSet<string> ForbiddenPasswordPhrases = new HashSet<string>(10, StringComparer.InvariantCultureIgnoreCase) { "archisteamfarm", "archi", "steam", "farm", "archisteam", "archifarm", "steamfarm", "asf", "asffarm", "password" }.ToFrozenSet(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
private static readonly JwtSecurityTokenHandler JwtSecurityTokenHandler = new();
|
[PublicAPI]
|
||||||
|
public static string GenerateChecksumFor(byte[] source) {
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
|
||||||
|
byte[] hash = SHA512.HashData(source);
|
||||||
|
|
||||||
|
return Convert.ToHexString(hash);
|
||||||
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
|
public static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
|
||||||
@@ -81,7 +90,7 @@ public static class Utilities {
|
|||||||
|
|
||||||
CookieCollection cookies = cookieContainer.GetCookies(uri);
|
CookieCollection cookies = cookieContainer.GetCookies(uri);
|
||||||
|
|
||||||
return cookies.Count > 0 ? cookies.FirstOrDefault(cookie => cookie.Name == name)?.Value : null;
|
return cookies.FirstOrDefault(cookie => cookie.Name == name)?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
@@ -113,7 +122,7 @@ public static class Utilities {
|
|||||||
|
|
||||||
switch (ASF.GlobalConfig?.OptimizationMode) {
|
switch (ASF.GlobalConfig?.OptimizationMode) {
|
||||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||||
List<T> results = new();
|
List<T> results = [];
|
||||||
|
|
||||||
foreach (Task<T> task in tasks) {
|
foreach (Task<T> task in tasks) {
|
||||||
results.Add(await task.ConfigureAwait(false));
|
results.Add(await task.ConfigureAwait(false));
|
||||||
@@ -169,19 +178,6 @@ public static class Utilities {
|
|||||||
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
|
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[PublicAPI]
|
|
||||||
public static JwtSecurityToken? ReadJwtToken(string token) {
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(token);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JwtSecurityTokenHandler.ReadJwtToken(token);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ASF.ArchiLogger.LogGenericException(e);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
|
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
|
||||||
ArgumentNullException.ThrowIfNull(document);
|
ArgumentNullException.ThrowIfNull(document);
|
||||||
@@ -250,6 +246,23 @@ public static class Utilities {
|
|||||||
return job.ToTask();
|
return job.ToTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PublicAPI]
|
||||||
|
public static bool TryReadJsonWebToken(string token, [NotNullWhen(true)] out JsonWebToken? result) {
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = new JsonWebToken(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||||
|
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||||
|
|
||||||
@@ -321,7 +334,7 @@ public static class Utilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result.Score < 4, suggestions is { Count: > 0 } ? string.Join(" ", suggestions.Where(static suggestion => suggestion.Length > 0)) : null);
|
return (result.Score < 4, suggestions is { Count: > 0 } ? string.Join(' ', suggestions.Where(static suggestion => suggestion.Length > 0)) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void WarnAboutIncompleteTranslation(ResourceManager resourceManager) {
|
internal static void WarnAboutIncompleteTranslation(ResourceManager resourceManager) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -35,7 +35,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
|||||||
|
|
||||||
private bool IsInitialized => InitializedAt > DateTime.MinValue;
|
private bool IsInitialized => InitializedAt > DateTime.MinValue;
|
||||||
private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan;
|
private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan;
|
||||||
private bool IsRecent => IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime);
|
private bool IsRecent => IsInitialized && (IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime));
|
||||||
|
|
||||||
private DateTime InitializedAt;
|
private DateTime InitializedAt;
|
||||||
private T? InitializedValue;
|
private T? InitializedValue;
|
||||||
@@ -55,7 +55,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
|||||||
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsInitialized && IsRecent) {
|
if (IsRecent) {
|
||||||
return (true, InitializedValue);
|
return (true, InitializedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (IsInitialized && IsRecent) {
|
if (IsRecent) {
|
||||||
return (true, InitializedValue);
|
return (true, InitializedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private (bool Success, T? Result) ReturnFailedValueFor(ECacheFallback cacheFallback, T? result = default) {
|
private (bool Success, T? Result) ReturnFailedValueFor(ECacheFallback cacheFallback, T? result = default) {
|
||||||
if (!Enum.IsDefined(typeof(ECacheFallback), cacheFallback)) {
|
if (!Enum.IsDefined(cacheFallback)) {
|
||||||
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Frozen;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -45,7 +45,7 @@ public static class ArchiCryptoHelper {
|
|||||||
|
|
||||||
internal static bool HasDefaultCryptKey { get; private set; } = true;
|
internal static bool HasDefaultCryptKey { get; private set; } = true;
|
||||||
|
|
||||||
private static readonly ImmutableHashSet<string> ForbiddenCryptKeyPhrases = ImmutableHashSet.Create(StringComparer.InvariantCultureIgnoreCase, "crypt", "key", "cryptkey");
|
private static readonly FrozenSet<string> ForbiddenCryptKeyPhrases = new HashSet<string>(3, StringComparer.InvariantCultureIgnoreCase) { "crypt", "key", "cryptkey" }.ToFrozenSet(StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
private static IEnumerable<byte> SteamParentalCharacters => Enumerable.Range('0', 10).Select(static character => (byte) character);
|
private static IEnumerable<byte> SteamParentalCharacters => Enumerable.Range('0', 10).Select(static character => (byte) character);
|
||||||
|
|
||||||
@@ -59,53 +59,53 @@ public static class ArchiCryptoHelper {
|
|||||||
|
|
||||||
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
||||||
|
|
||||||
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string encryptedString) {
|
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string text) {
|
||||||
if (!Enum.IsDefined(cryptoMethod)) {
|
if (!Enum.IsDefined(cryptoMethod)) {
|
||||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
return cryptoMethod switch {
|
return cryptoMethod switch {
|
||||||
ECryptoMethod.AES => DecryptAES(encryptedString),
|
ECryptoMethod.AES => DecryptAES(text),
|
||||||
ECryptoMethod.EnvironmentVariable => Environment.GetEnvironmentVariable(encryptedString)?.Trim(),
|
ECryptoMethod.EnvironmentVariable => Environment.GetEnvironmentVariable(text)?.Trim(),
|
||||||
ECryptoMethod.File => await ReadFromFile(encryptedString).ConfigureAwait(false),
|
ECryptoMethod.File => await ReadFromFile(text).ConfigureAwait(false),
|
||||||
ECryptoMethod.PlainText => encryptedString,
|
ECryptoMethod.PlainText => text,
|
||||||
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(encryptedString),
|
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(text),
|
||||||
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string? Encrypt(ECryptoMethod cryptoMethod, string decryptedString) {
|
internal static string? Encrypt(ECryptoMethod cryptoMethod, string text) {
|
||||||
if (!Enum.IsDefined(cryptoMethod)) {
|
if (!Enum.IsDefined(cryptoMethod)) {
|
||||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
return cryptoMethod switch {
|
return cryptoMethod switch {
|
||||||
ECryptoMethod.AES => EncryptAES(decryptedString),
|
ECryptoMethod.AES => EncryptAES(text),
|
||||||
ECryptoMethod.EnvironmentVariable => decryptedString,
|
ECryptoMethod.EnvironmentVariable => text,
|
||||||
ECryptoMethod.File => decryptedString,
|
ECryptoMethod.File => text,
|
||||||
ECryptoMethod.PlainText => decryptedString,
|
ECryptoMethod.PlainText => text,
|
||||||
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decryptedString),
|
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(text),
|
||||||
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string Hash(EHashingMethod hashingMethod, string stringToHash) {
|
internal static string Hash(EHashingMethod hashingMethod, string text) {
|
||||||
if (!Enum.IsDefined(hashingMethod)) {
|
if (!Enum.IsDefined(hashingMethod)) {
|
||||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(stringToHash);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
if (hashingMethod == EHashingMethod.PlainText) {
|
if (hashingMethod == EHashingMethod.PlainText) {
|
||||||
return stringToHash;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(stringToHash);
|
byte[] textBytes = Encoding.UTF8.GetBytes(text);
|
||||||
byte[] hashBytes = Hash(passwordBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
byte[] hashBytes = Hash(textBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
||||||
|
|
||||||
return Convert.ToBase64String(hashBytes);
|
return Convert.ToBase64String(hashBytes);
|
||||||
}
|
}
|
||||||
@@ -197,13 +197,31 @@ public static class ArchiCryptoHelper {
|
|||||||
EncryptionKey = encryptionKey;
|
EncryptionKey = encryptionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? DecryptAES(string encryptedString) {
|
internal static bool VerifyHash(EHashingMethod hashingMethod, string text, string hash) {
|
||||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
if (!Enum.IsDefined(hashingMethod)) {
|
||||||
|
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(hash);
|
||||||
|
|
||||||
|
// Text is always provided as plain text
|
||||||
|
byte[] textBytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
textBytes = Hash(textBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
||||||
|
|
||||||
|
// Hash is either plain text password (when EHashingMethod.PlainText), or base64-encoded hash
|
||||||
|
byte[] hashBytes = hashingMethod == EHashingMethod.PlainText ? Encoding.UTF8.GetBytes(hash) : Convert.FromBase64String(hash);
|
||||||
|
|
||||||
|
return CryptographicOperations.FixedTimeEquals(textBytes, hashBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? DecryptAES(string text) {
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] key = SHA256.HashData(EncryptionKey);
|
byte[] key = SHA256.HashData(EncryptionKey);
|
||||||
|
|
||||||
byte[] decryptedData = Convert.FromBase64String(encryptedString);
|
byte[] decryptedData = Convert.FromBase64String(text);
|
||||||
decryptedData = CryptoHelper.SymmetricDecrypt(decryptedData, key);
|
decryptedData = CryptoHelper.SymmetricDecrypt(decryptedData, key);
|
||||||
|
|
||||||
return Encoding.UTF8.GetString(decryptedData);
|
return Encoding.UTF8.GetString(decryptedData);
|
||||||
@@ -214,8 +232,8 @@ public static class ArchiCryptoHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? DecryptProtectedDataForCurrentUser(string encryptedString) {
|
private static string? DecryptProtectedDataForCurrentUser(string text) {
|
||||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows()) {
|
if (!OperatingSystem.IsWindows()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -223,7 +241,7 @@ public static class ArchiCryptoHelper {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] decryptedData = ProtectedData.Unprotect(
|
byte[] decryptedData = ProtectedData.Unprotect(
|
||||||
Convert.FromBase64String(encryptedString),
|
Convert.FromBase64String(text),
|
||||||
EncryptionKey,
|
EncryptionKey,
|
||||||
DataProtectionScope.CurrentUser
|
DataProtectionScope.CurrentUser
|
||||||
);
|
);
|
||||||
@@ -236,13 +254,13 @@ public static class ArchiCryptoHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? EncryptAES(string decryptedString) {
|
private static string? EncryptAES(string text) {
|
||||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] key = SHA256.HashData(EncryptionKey);
|
byte[] key = SHA256.HashData(EncryptionKey);
|
||||||
|
|
||||||
byte[] encryptedData = Encoding.UTF8.GetBytes(decryptedString);
|
byte[] encryptedData = Encoding.UTF8.GetBytes(text);
|
||||||
encryptedData = CryptoHelper.SymmetricEncrypt(encryptedData, key);
|
encryptedData = CryptoHelper.SymmetricEncrypt(encryptedData, key);
|
||||||
|
|
||||||
return Convert.ToBase64String(encryptedData);
|
return Convert.ToBase64String(encryptedData);
|
||||||
@@ -253,8 +271,8 @@ public static class ArchiCryptoHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? EncryptProtectedDataForCurrentUser(string decryptedString) {
|
private static string? EncryptProtectedDataForCurrentUser(string text) {
|
||||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows()) {
|
if (!OperatingSystem.IsWindows()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -262,7 +280,7 @@ public static class ArchiCryptoHelper {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] encryptedData = ProtectedData.Protect(
|
byte[] encryptedData = ProtectedData.Protect(
|
||||||
Encoding.UTF8.GetBytes(decryptedString),
|
Encoding.UTF8.GetBytes(text),
|
||||||
EncryptionKey,
|
EncryptionKey,
|
||||||
DataProtectionScope.CurrentUser
|
DataProtectionScope.CurrentUser
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
// |
|
// |
|
||||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
// Contact: JustArchi@JustArchi.net
|
// Contact: JustArchi@JustArchi.net
|
||||||
// |
|
// |
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
56
ArchiSteamFarm/Helpers/Json/GuidJsonConverter.cs
Normal file
56
ArchiSteamFarm/Helpers/Json/GuidJsonConverter.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// _ _ _ ____ _ _____
|
||||||
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||||
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||||
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||||
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||||
|
// |
|
||||||
|
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||||
|
// Contact: JustArchi@JustArchi.net
|
||||||
|
// |
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// |
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// |
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ArchiSteamFarm.Helpers.Json;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// TODO: This class exists purely because STJ can't deserialize Guid in other formats than default, at least for now
|
||||||
|
/// https://github.com/dotnet/runtime/issues/30692
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class GuidJsonConverter : JsonConverter<Guid> {
|
||||||
|
internal static readonly GuidJsonConverter Shared = new();
|
||||||
|
|
||||||
|
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
if (reader.TryGetGuid(out Guid result)) {
|
||||||
|
// Great, we can work with it
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try again using more flexible implementation, sigh
|
||||||
|
return Guid.Parse(reader.GetString()!);
|
||||||
|
} catch {
|
||||||
|
// Throw JsonException instead, which will be converted into standard message by STJ
|
||||||
|
throw new JsonException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) {
|
||||||
|
ArgumentNullException.ThrowIfNull(writer);
|
||||||
|
|
||||||
|
writer.WriteStringValue(value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user