mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 06:20:34 +00:00
Compare commits
491 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
120f7e36d6 | ||
|
|
2e9791faa3 | ||
|
|
fe8e125b86 | ||
|
|
16f4dff0c2 | ||
|
|
915693d7fa | ||
|
|
52adac7cab | ||
|
|
a066b952d7 | ||
|
|
cb0e2218a6 | ||
|
|
f9949967dc | ||
|
|
0bf1b7af91 | ||
|
|
193f0e3c08 | ||
|
|
5cbe11241c | ||
|
|
03fc9cb4be | ||
|
|
2ed7cdf98e | ||
|
|
305d6a2d17 | ||
|
|
6f784621ed | ||
|
|
83d6d43845 | ||
|
|
90e9f47899 | ||
|
|
185d7adccf | ||
|
|
977f571db6 | ||
|
|
1c22a45cb6 | ||
|
|
608229f962 | ||
|
|
3f04144e41 | ||
|
|
ac9d4a7783 | ||
|
|
adbf0748f8 | ||
|
|
263c77da12 | ||
|
|
103c543eea | ||
|
|
0ae03c7cd5 | ||
|
|
f2ff2f4929 | ||
|
|
b34f18497d | ||
|
|
1e04acb904 | ||
|
|
2b06153fa2 | ||
|
|
e1c364cf7a | ||
|
|
e281a04450 | ||
|
|
b40d55c2e4 | ||
|
|
3e96f37979 | ||
|
|
72703dc61d | ||
|
|
4ba2f16907 | ||
|
|
d6a7c9a843 | ||
|
|
9a304a3992 | ||
|
|
be4a625dc2 | ||
|
|
e44b7d3248 | ||
|
|
fc13f2c488 | ||
|
|
494b1dfd1b | ||
|
|
eb41dace9f | ||
|
|
2b2607cfcd | ||
|
|
bde696124b | ||
|
|
1f4f50591a | ||
|
|
5377598226 | ||
|
|
050d8dca12 | ||
|
|
54e31504d5 | ||
|
|
d3d8a6ffcb | ||
|
|
4518547543 | ||
|
|
1061676462 | ||
|
|
1d7794590e | ||
|
|
5d32eb6325 | ||
|
|
3a9e9de596 | ||
|
|
b46e936ac6 | ||
|
|
fc46c65bdf | ||
|
|
57de1f3c10 | ||
|
|
a59c3d8fd2 | ||
|
|
6c774f5980 | ||
|
|
b126c5fd75 | ||
|
|
c21e4fbdb8 | ||
|
|
ad4c81ac02 | ||
|
|
17796c3466 | ||
|
|
09804a5032 | ||
|
|
e4a650c8e0 | ||
|
|
5490539480 | ||
|
|
2a231982ed | ||
|
|
5219fc8958 | ||
|
|
2f5fdf3185 | ||
|
|
5a5a6e2074 | ||
|
|
a1cf8291b4 | ||
|
|
1a16589c0a | ||
|
|
7372601064 | ||
|
|
651d0bdb4b | ||
|
|
25e43d61e6 | ||
|
|
dfc3c0eef3 | ||
|
|
c20b0c2c5b | ||
|
|
4a57e3a9c1 | ||
|
|
1cde3afcbf | ||
|
|
b6274958bd | ||
|
|
2023103ed6 | ||
|
|
d5cb0baf1f | ||
|
|
3b7a766ce4 | ||
|
|
ac8fa21c90 | ||
|
|
4abf0388d6 | ||
|
|
82c294bc85 | ||
|
|
f7ebc4acd5 | ||
|
|
910ec7ad29 | ||
|
|
6aaac31da0 | ||
|
|
0708199a59 | ||
|
|
907f6e4b33 | ||
|
|
78e30343f7 | ||
|
|
058e9fca77 | ||
|
|
e97c660b08 | ||
|
|
b4dcc8621a | ||
|
|
a50bc1e13b | ||
|
|
502af043df | ||
|
|
f3ef7d0397 | ||
|
|
8d3edb7d60 | ||
|
|
8bae812ae5 | ||
|
|
b4db6584e8 | ||
|
|
77f6fabbc7 | ||
|
|
7f4a11bb6a | ||
|
|
93402cc6a0 | ||
|
|
1f2607149b | ||
|
|
02312dbee5 | ||
|
|
aa1c344b03 | ||
|
|
0df46a08da | ||
|
|
ce2693d407 | ||
|
|
e1c3268cf4 | ||
|
|
6a0d428fa3 | ||
|
|
776c5fe31c | ||
|
|
22177f1fe7 | ||
|
|
ab1edae9c7 | ||
|
|
53d80345d3 | ||
|
|
d571cd9580 | ||
|
|
4106c5f41a | ||
|
|
71fa7560f7 | ||
|
|
32196a53cd | ||
|
|
5d467aca9a | ||
|
|
52eacaf577 | ||
|
|
d44c57a48f | ||
|
|
629a750df6 | ||
|
|
21b1a319ca | ||
|
|
bb1f02e788 | ||
|
|
5d8826b71b | ||
|
|
829e5ac4fb | ||
|
|
feec46da32 | ||
|
|
4288f35e99 | ||
|
|
69004d20ce | ||
|
|
91ce53d283 | ||
|
|
400ea64cfd | ||
|
|
1b047dfbff | ||
|
|
f9cd805304 | ||
|
|
74e2ad2f40 | ||
|
|
0242ed9f10 | ||
|
|
b31c8266eb | ||
|
|
ddd55fd4d9 | ||
|
|
a324a69371 | ||
|
|
de67195185 | ||
|
|
290f16b3fe |
@@ -36,7 +36,7 @@ csharp_prefer_simple_default_expression = true:warning
|
||||
csharp_prefer_simple_using_statement = 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_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_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = none
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
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_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_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_local_over_anonymous_function = true:warning
|
||||
csharp_style_prefer_method_group_conversion = 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_primary_constructors = 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_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
|
||||
|
||||
@@ -98,13 +109,12 @@ csharp_style_var_elsewhere = false:warning
|
||||
csharp_style_var_for_built_in_types = 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 #
|
||||
###############################
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_analyzer_diagnostic.severity = warning
|
||||
|
||||
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.ca1031.severity = silent
|
||||
dotnet_diagnostic.ca1863.severity = silent
|
||||
|
||||
# Rule - almost everything
|
||||
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_collection_initializer = true:warning
|
||||
dotnet_style_explicit_tuple_names = true:warning
|
||||
dotnet_style_namespace_match_folder = true:warning
|
||||
dotnet_style_null_propagation = true:warning
|
||||
dotnet_style_object_initializer = true:warning
|
||||
|
||||
@@ -218,7 +230,7 @@ dotnet_style_require_accessibility_modifiers = always:warning
|
||||
# JetBrains, IntelliJ/Rider #
|
||||
###############################
|
||||
|
||||
[*.{csproj,props,xml}]
|
||||
[*.{csproj,props,resx,xml}]
|
||||
ij_xml_keep_blank_lines = 1
|
||||
ij_xml_keep_line_breaks = false
|
||||
ij_xml_keep_line_breaks_in_text = false
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
@@ -12,6 +12,8 @@ body:
|
||||
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
|
||||
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)**
|
||||
required: true
|
||||
- label: I'm not using **[custom plugins](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Plugins)**
|
||||
@@ -42,12 +44,12 @@ body:
|
||||
- docker-linux/arm/v7
|
||||
- docker-linux/arm64
|
||||
- generic (with latest .NET runtime)
|
||||
- generic-netf (with latest Mono runtime)
|
||||
- linux-arm
|
||||
- linux-arm64
|
||||
- linux-x64
|
||||
- osx-arm64
|
||||
- osx-x64
|
||||
- win-arm64
|
||||
- win-x64
|
||||
validations:
|
||||
required: true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/Enhancement-idea.yml
vendored
2
.github/ISSUE_TEMPLATE/Enhancement-idea.yml
vendored
@@ -12,6 +12,8 @@ body:
|
||||
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
|
||||
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)**
|
||||
required: true
|
||||
- label: I believe that my idea falls into ASF's scope and should be offered as part of ASF built-in functionality
|
||||
|
||||
4
.github/crowdin.yml
vendored
4
.github/crowdin.yml
vendored
@@ -5,6 +5,7 @@
|
||||
"source": "/ArchiSteamFarm/Localization/Strings.resx",
|
||||
"translation": "/ArchiSteamFarm/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".bs-BA.resx": ".bs-Latn.resx",
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
@@ -16,6 +17,7 @@
|
||||
"source": "/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.resx",
|
||||
"translation": "/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".bs-BA.resx": ".bs-Latn.resx",
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
@@ -27,6 +29,7 @@
|
||||
"source": "/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Localization/Strings.resx",
|
||||
"translation": "/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".bs-BA.resx": ".bs-Latn.resx",
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
@@ -38,6 +41,7 @@
|
||||
"source": "/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.resx",
|
||||
"translation": "/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".bs-BA.resx": ".bs-Latn.resx",
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -5,7 +5,9 @@ on: [push, pull_request]
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_SDK_VERSION: 7.0.x
|
||||
DOTNET_SDK_VERSION: 8.0
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
@@ -19,13 +21,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -40,7 +42,7 @@ jobs:
|
||||
|
||||
- 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-') }}
|
||||
uses: crowdin/github-action@v1.13.1
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
config: '.github/crowdin.yml'
|
||||
|
||||
22
.github/workflows/code-quality.yml
vendored
22
.github/workflows/code-quality.yml
vendored
@@ -6,24 +6,40 @@ env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Checkout code (for PR)
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 100 # History is required for pull request analysis
|
||||
ref: ${{ github.event.pull_request.head.sha }} # To check out the actual pull request commit, not the merge commit
|
||||
show-progress: false
|
||||
|
||||
- name: Run Qodana scan
|
||||
uses: JetBrains/qodana-action@v2023.2.8
|
||||
uses: JetBrains/qodana-action@v2023.3.1
|
||||
with:
|
||||
args: --property=idea.headless.enable.statistics=false
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
- name: Report Qodana results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v2.22.1
|
||||
uses: github/codeql-action/upload-sarif@v3.24.4
|
||||
with:
|
||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||
|
||||
6
.github/workflows/docker-ci.yml
vendored
6
.github/workflows/docker-ci.yml
vendored
@@ -5,6 +5,8 @@ on: [push, pull_request]
|
||||
env:
|
||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
main:
|
||||
strategy:
|
||||
@@ -17,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
@@ -26,7 +28,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
|
||||
7
.github/workflows/docker-publish-latest.yml
vendored
7
.github/workflows/docker-publish-latest.yml
vendored
@@ -9,13 +9,16 @@ env:
|
||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||
TAG: latest
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
@@ -56,7 +59,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile.Service
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.Service
|
||||
|
||||
9
.github/workflows/docker-publish-main.yml
vendored
9
.github/workflows/docker-publish-main.yml
vendored
@@ -10,13 +10,16 @@ env:
|
||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||
TAG: main
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
@@ -56,7 +59,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
@@ -71,7 +74,7 @@ jobs:
|
||||
push: true
|
||||
|
||||
- name: Update DockerHub repository description
|
||||
uses: peter-evans/dockerhub-description@v3.4.2
|
||||
uses: peter-evans/dockerhub-description@v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
@@ -10,13 +10,16 @@ env:
|
||||
PLATFORMS: linux/amd64,linux/arm,linux/arm64
|
||||
TAG: released
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
@@ -57,7 +60,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
12
.github/workflows/lock-threads.yml
vendored
12
.github/workflows/lock-threads.yml
vendored
@@ -3,13 +3,23 @@ name: ASF-lock-threads
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
discussions: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Lock inactive threads
|
||||
uses: dessant/lock-threads@v4.0.1
|
||||
uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
discussion-inactive-days: 90
|
||||
issue-inactive-days: 60
|
||||
pr-inactive-days: 60
|
||||
|
||||
100
.github/workflows/publish.yml
vendored
100
.github/workflows/publish.yml
vendored
@@ -4,25 +4,27 @@ on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CONFIGURATION: Release
|
||||
DOTNET_SDK_VERSION: 7.0.x
|
||||
NET_CORE_VERSION: net7.0
|
||||
NET_FRAMEWORK_VERSION: net481
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_SDK_VERSION: 8.0
|
||||
NODE_JS_VERSION: 'lts/*'
|
||||
PLUGINS: ArchiSteamFarm.OfficialPlugins.ItemsMatcher ArchiSteamFarm.OfficialPlugins.MobileAuthenticator ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish-asf-ui:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js with npm
|
||||
uses: actions/setup-node@v3.8.1
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
check-latest: true
|
||||
node-version: ${{ env.NODE_JS_VERSION }}
|
||||
@@ -40,7 +42,7 @@ jobs:
|
||||
run: npm run-script deploy --no-progress --prefix ASF-ui
|
||||
|
||||
- name: Upload ASF-ui
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: ASF-ui
|
||||
path: ASF-ui/dist
|
||||
@@ -54,8 +56,6 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
variant: generic
|
||||
- os: windows-latest
|
||||
variant: generic-netf
|
||||
- os: ubuntu-latest
|
||||
variant: linux-arm
|
||||
- os: ubuntu-latest
|
||||
@@ -73,18 +73,14 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -92,7 +88,7 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Download previously built ASF-ui
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ASF-ui
|
||||
path: ASF-ui/dist
|
||||
@@ -131,19 +127,15 @@ jobs:
|
||||
|
||||
- name: Prepare for publishing on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dotnet restore
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$TARGET_FRAMEWORK" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Prepare for publishing on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
env:
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -151,7 +143,16 @@ jobs:
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet restore
|
||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:TARGET_FRAMEWORK" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
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
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
@@ -184,13 +185,12 @@ jobs:
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
MAX_JOBS: 4
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
publish() {
|
||||
dotnet publish "$1" -c "$CONFIGURATION" -f "$TARGET_FRAMEWORK" -o "out/${1}/${TARGET_FRAMEWORK}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
dotnet publish "$1" -c "$CONFIGURATION" -o "out/${1}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
}
|
||||
|
||||
for plugin in $PLUGINS; do
|
||||
@@ -207,7 +207,6 @@ jobs:
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
env:
|
||||
MAX_JOBS: 4
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -223,7 +222,7 @@ jobs:
|
||||
|
||||
Set-Location "$env:GITHUB_WORKSPACE"
|
||||
|
||||
dotnet publish "$plugin" -c "$env:CONFIGURATION" -f "$env:TARGET_FRAMEWORK" -o "out\$plugin\$env:TARGET_FRAMEWORK" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
dotnet publish "$plugin" -c "$env:CONFIGURATION" -o "out\$plugin" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
@@ -248,7 +247,6 @@ jobs:
|
||||
- name: Publish ASF-${{ matrix.variant }} on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
VARIANT: ${{ matrix.variant }}
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -260,13 +258,13 @@ jobs:
|
||||
variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $VARIANT --self-contained"
|
||||
fi
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$TARGET_FRAMEWORK" -o "out/${VARIANT}" "-p:ASFVariant=${VARIANT}" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -o "out/${VARIANT}" "-p:ASFVariant=${VARIANT}" -p:ContinuousIntegrationBuild=true --nologo $variantArgs
|
||||
|
||||
# If we're including official plugins for this framework, copy them to output directory
|
||||
for plugin in $PLUGINS; do
|
||||
if [ -d "out/${plugin}/${TARGET_FRAMEWORK}" ]; then
|
||||
if [ -d "out/${plugin}" ]; then
|
||||
mkdir -p "out/${VARIANT}/plugins/${plugin}"
|
||||
cp -pR "out/${plugin}/${TARGET_FRAMEWORK}/"* "out/${VARIANT}/plugins/${plugin}"
|
||||
cp -pR "out/${plugin}/"* "out/${VARIANT}/plugins/${plugin}"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -328,7 +326,6 @@ jobs:
|
||||
- name: Publish ASF-${{ matrix.variant }} on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
env:
|
||||
TARGET_FRAMEWORK: ${{ (endsWith(matrix.variant, '-netf') && env.NET_FRAMEWORK_VERSION) || env.NET_CORE_VERSION }}
|
||||
VARIANT: ${{ matrix.variant }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -342,7 +339,7 @@ jobs:
|
||||
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$env:VARIANT", '--self-contained'
|
||||
}
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:TARGET_FRAMEWORK" -o "out\$env:VARIANT" "-p:ASFVariant=$env:VARIANT" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -o "out\$env:VARIANT" "-p:ASFVariant=$env:VARIANT" -p:ContinuousIntegrationBuild=true --nologo $variantArgs
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
@@ -350,12 +347,12 @@ jobs:
|
||||
|
||||
# If we're including official plugins for this framework, copy them to output directory
|
||||
foreach ($plugin in $env:PLUGINS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
if (Test-Path "out\$plugin\$env:TARGET_FRAMEWORK" -PathType Container) {
|
||||
if (Test-Path "out\$plugin" -PathType Container) {
|
||||
if (!(Test-Path "out\$env:VARIANT\plugins\$plugin" -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path "out\$env:VARIANT\plugins\$plugin" > $null
|
||||
}
|
||||
|
||||
Copy-Item "out\$plugin\$env:TARGET_FRAMEWORK\*" "out\$env:VARIANT\plugins\$plugin" -Recurse
|
||||
Copy-Item "out\$plugin\*" "out\$env:VARIANT\plugins\$plugin" -Recurse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +405,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload ASF-${{ matrix.variant }}
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
|
||||
path: out/ASF-${{ matrix.variant }}.zip
|
||||
@@ -418,68 +415,65 @@ jobs:
|
||||
needs: publish-asf
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Download ASF-generic artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-generic
|
||||
path: out
|
||||
|
||||
- name: Download ASF-generic-netf artifact from windows-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
with:
|
||||
name: windows-latest_ASF-generic-netf
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-arm
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-x64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-arm64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: macos-latest_ASF-osx-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-x64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: macos-latest_ASF-osx-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-win-arm64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: windows-latest_ASF-win-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-win-x64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: windows-latest_ASF-win-x64
|
||||
path: out
|
||||
|
||||
- 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:
|
||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||
|
||||
@@ -496,19 +490,19 @@ jobs:
|
||||
)
|
||||
|
||||
- name: Upload SHA512SUMS
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: SHA512SUMS
|
||||
path: out/SHA512SUMS
|
||||
|
||||
- name: Upload SHA512SUMS.sign
|
||||
uses: actions/upload-artifact@v3.1.3
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: SHA512SUMS.sign
|
||||
path: out/SHA512SUMS.sign
|
||||
|
||||
- name: Create ArchiSteamFarm GitHub release
|
||||
uses: ncipollo/release-action@v1.13.0
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
artifacts: "out/*"
|
||||
bodyFile: .github/RELEASE_TEMPLATE.md
|
||||
|
||||
9
.github/workflows/translations.yml
vendored
9
.github/workflows/translations.yml
vendored
@@ -4,13 +4,16 @@ on:
|
||||
schedule:
|
||||
- cron: '55 1 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
show-progress: false
|
||||
submodules: recursive
|
||||
@@ -27,7 +30,7 @@ jobs:
|
||||
git reset --hard origin/master
|
||||
|
||||
- name: Download latest translations from Crowdin
|
||||
uses: crowdin/github-action@v1.13.1
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
@@ -39,7 +42,7 @@ jobs:
|
||||
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
|
||||
|
||||
- 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:
|
||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||
git_config_global: true
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: be9b550cef...cd1173a0d6
@@ -6,18 +6,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,6 +22,7 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
@@ -34,12 +35,12 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
internal static class CatAPI {
|
||||
private const string URL = "https://api.thecatapi.com";
|
||||
|
||||
internal static async Task<Uri?> GetRandomCatURL(WebBrowser webBrowser) {
|
||||
internal static async Task<Uri?> GetRandomCatURL(WebBrowser webBrowser, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
Uri request = new($"{URL}/v1/images/search");
|
||||
|
||||
ObjectResponse<ImmutableList<MeowResponse>>? response = await webBrowser.UrlGetToJsonObject<ImmutableList<MeowResponse>>(request).ConfigureAwait(false);
|
||||
ObjectResponse<ImmutableList<MeowResponse>>? response = await webBrowser.UrlGetToJsonObject<ImmutableList<MeowResponse>>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response?.Content?.FirstOrDefault()?.URL;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Controllers.Api;
|
||||
@@ -38,14 +39,16 @@ public sealed class CatController : ArchiController {
|
||||
/// Fetches URL of a random cat picture.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(GenericResponse<Uri>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
[ProducesResponseType<GenericResponse<Uri>>((int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> CatGet() {
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
Uri? url = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
Uri? url = await CatAPI.GetRandomCatURL(ASF.WebBrowser, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return url != null ? Ok(new GenericResponse<Uri>(url)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,16 +21,17 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
@@ -45,31 +46,35 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
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
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
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)
|
||||
[JsonProperty]
|
||||
public bool CustomIsEnabledField { get; private set; } = true;
|
||||
[JsonInclude]
|
||||
[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
|
||||
// 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
|
||||
// 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) {
|
||||
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
|
||||
switch (configProperty) {
|
||||
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.Type == JTokenType.Boolean:
|
||||
bool exampleBooleanValue = configValue.Value<bool>();
|
||||
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of: {exampleBooleanValue}");
|
||||
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.ValueKind == JsonValueKind.True:
|
||||
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of true");
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -133,9 +138,9 @@ internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection,
|
||||
// This method, apart from being called during bot modules initialization, allows you to read custom bot config properties that are not recognized by ASF
|
||||
// Thanks to that, you can extend default bot 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
|
||||
// Also keep in mind that this function can be called multiple times, e.g. when user edits his 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
|
||||
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()
|
||||
// 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");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,15 +21,17 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class MeowResponse {
|
||||
[JsonProperty("url", Required = Required.Always)]
|
||||
internal readonly Uri URL = null!;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("url")]
|
||||
[JsonRequired]
|
||||
internal Uri URL { get; private init; } = null!;
|
||||
|
||||
[JsonConstructor]
|
||||
private MeowResponse() { }
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,8 +20,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Runtime;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
@@ -38,8 +40,12 @@ internal sealed class PeriodicGCPlugin : IPlugin {
|
||||
private static readonly object LockObject = new();
|
||||
private static readonly Timer PeriodicGCTimer = new(PerformGC);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string Name => nameof(PeriodicGCPlugin);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public Task OnLoaded() {
|
||||
|
||||
@@ -7,21 +7,9 @@
|
||||
<PackageReference Include="AngleSharp.XPath" IncludeAssets="compile" />
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<Reference Include="System.Net.Http" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.Net.Http.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,11 +20,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||
|
||||
public sealed class SignInWithSteamRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri RedirectURL { get; private set; } = null!;
|
||||
[JsonInclude]
|
||||
[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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,13 +20,18 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||
|
||||
public sealed class SignInWithSteamResponse {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri ReturnURL { get; private set; }
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
public Uri ReturnURL { get; private init; }
|
||||
|
||||
internal SignInWithSteamResponse(Uri returnURL) => ReturnURL = returnURL ?? throw new ArgumentNullException(nameof(returnURL));
|
||||
internal SignInWithSteamResponse(Uri returnURL) {
|
||||
ArgumentNullException.ThrowIfNull(returnURL);
|
||||
|
||||
ReturnURL = returnURL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -41,14 +41,11 @@ namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam;
|
||||
[Route("/Api/Bot/{botName:required}/SignInWithSteam")]
|
||||
public sealed class SignInWithSteamController : ArchiController {
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(GenericResponse<SignInWithSteamResponse>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
[ProducesResponseType<GenericResponse<SignInWithSteamResponse>>((int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> Post(string botName, [FromBody] SignInWithSteamRequest request) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
throw new ArgumentNullException(nameof(botName));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(botName);
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
Bot? bot = Bot.GetBot(botName);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,7 +20,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
@@ -31,8 +33,12 @@ namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam;
|
||||
[Export(typeof(IPlugin))]
|
||||
[UsedImplicitly]
|
||||
internal sealed class SignInWithSteamPlugin : IPlugin {
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string Name => nameof(SignInWithSteamPlugin);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public Version Version => typeof(SignInWithSteamPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public Task OnLoaded() {
|
||||
|
||||
@@ -6,22 +6,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Linq.Async" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<Reference Include="System.Net.Http" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.Net.Http.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,7 +22,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
@@ -38,45 +41,96 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
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) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(inventory);
|
||||
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
if (totalInventoryCount == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(totalInventoryCount));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
throw new ArgumentNullException(nameof(tradeToken));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
||||
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
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) {
|
||||
if (licenseID == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(licenseID));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
@@ -105,6 +159,28 @@ internal static class Backend {
|
||||
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) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
@@ -115,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);
|
||||
}
|
||||
|
||||
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
|
||||
// |
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
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)) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(BotConfig.ETradingPreferences.MatchActively)));
|
||||
if (!ItemsMatcherPlugin.RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(remoteCommunication)));
|
||||
}
|
||||
|
||||
remoteCommunication.TriggerMatchActivelyEarlier();
|
||||
@@ -98,9 +106,7 @@ internal static class Commands {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botNames)) {
|
||||
throw new ArgumentNullException(nameof(botNames));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(botNames);
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -114,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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,78 +22,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class AnnouncementRequest {
|
||||
[JsonProperty]
|
||||
private readonly string? AvatarHash;
|
||||
internal class AnnouncementRequest {
|
||||
[JsonInclude]
|
||||
private string? AvatarHash { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private Guid Guid { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableList<AssetForListing> Inventory;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<AssetForListing> Inventory { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private string InventoryChecksum { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly bool MatchEverything;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<Asset.EType> MatchableTypes { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly byte MaxTradeHoldDuration;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private bool MatchEverything { get; init; }
|
||||
|
||||
[JsonProperty]
|
||||
private readonly string? Nickname;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private byte MaxTradeHoldDuration { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
private string? Nickname { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly uint TotalInventoryCount;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ulong SteamID { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly string TradeToken;
|
||||
[JsonInclude]
|
||||
[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) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
[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);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
throw new ArgumentNullException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(inventory);
|
||||
ArgumentException.ThrowIfNullOrEmpty(inventoryChecksum);
|
||||
|
||||
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(matchableTypes));
|
||||
}
|
||||
|
||||
if (totalInventoryCount == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(totalInventoryCount));
|
||||
ArgumentOutOfRangeException.ThrowIfZero(totalInventoryCount);
|
||||
ArgumentException.ThrowIfNullOrEmpty(tradeToken);
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
Guid = guid;
|
||||
SteamID = steamID;
|
||||
TradeToken = tradeToken;
|
||||
Inventory = inventory.ToImmutableList();
|
||||
Inventory = inventory.ToImmutableHashSet();
|
||||
InventoryChecksum = inventoryChecksum;
|
||||
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
||||
MatchEverything = matchEverything;
|
||||
MaxTradeHoldDuration = maxTradeHoldDuration;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,18 +20,31 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class AssetForListing : AssetInInventory {
|
||||
[JsonProperty("l", Required = Required.Always)]
|
||||
internal readonly ulong PreviousAssetID;
|
||||
internal string BackendHashCode => $"{Index}-{PreviousAssetID}-{AssetID}-{ClassID}-{Rarity}-{RealAppID}-{Tradable}-{Type}-{Amount}";
|
||||
|
||||
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);
|
||||
|
||||
Index = index;
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,29 +20,41 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AssetForMatching {
|
||||
[JsonProperty("a", Required = Required.Always)]
|
||||
internal readonly uint Amount;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("a")]
|
||||
[JsonRequired]
|
||||
internal uint Amount { get; set; }
|
||||
|
||||
[JsonProperty("c", Required = Required.Always)]
|
||||
internal readonly ulong ClassID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("c")]
|
||||
[JsonRequired]
|
||||
internal ulong ClassID { get; private init; }
|
||||
|
||||
[JsonProperty("r", Required = Required.Always)]
|
||||
internal readonly Asset.ERarity Rarity;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("r")]
|
||||
[JsonRequired]
|
||||
internal Asset.ERarity Rarity { get; private init; }
|
||||
|
||||
[JsonProperty("e", Required = Required.Always)]
|
||||
internal readonly uint RealAppID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("e")]
|
||||
[JsonRequired]
|
||||
internal uint RealAppID { get; private init; }
|
||||
|
||||
[JsonProperty("t", Required = Required.Always)]
|
||||
internal readonly bool Tradable;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("t")]
|
||||
[JsonRequired]
|
||||
internal bool Tradable { get; private init; }
|
||||
|
||||
[JsonProperty("p", Required = Required.Always)]
|
||||
internal readonly Asset.EType Type;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("p")]
|
||||
[JsonRequired]
|
||||
internal Asset.EType Type { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
protected AssetForMatching() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,14 +20,19 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AssetInInventory : AssetForMatching {
|
||||
[JsonProperty("d", Required = Required.Always)]
|
||||
internal readonly ulong AssetID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("d")]
|
||||
[JsonRequired]
|
||||
internal ulong AssetID { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
protected AssetInInventory() { }
|
||||
|
||||
internal AssetInInventory(Asset asset) : base(asset) {
|
||||
ArgumentNullException.ThrowIfNull(asset);
|
||||
@@ -35,8 +40,5 @@ internal class AssetInInventory : AssetForMatching {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,25 +19,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
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")]
|
||||
internal sealed class AccessTokenResponse : ResultResponse {
|
||||
[JsonProperty("data", Required = Required.Always)]
|
||||
internal readonly AccessTokenData Data = new();
|
||||
internal sealed class BackgroundTaskResponse {
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal bool Finished { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid RequestID { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private AccessTokenResponse() { }
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class AccessTokenData {
|
||||
[JsonProperty("webapi_token", Required = Required.Always)]
|
||||
internal readonly string WebAPIToken = "";
|
||||
|
||||
[JsonConstructor]
|
||||
internal AccessTokenData() { }
|
||||
}
|
||||
private BackgroundTaskResponse() { }
|
||||
}
|
||||
#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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,22 +20,22 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class HeartBeatRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid Guid { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal HeartBeatRequest(Guid guid, ulong steamID) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,29 +23,31 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class InventoriesRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid Guid { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetForMatching> Inventory;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<AssetForMatching> Inventory { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,47 +21,48 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class ListedUser {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetInInventory> Assets = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<AssetInInventory> Assets { get; private init; } = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes = ImmutableHashSet<Asset.EType>.Empty;
|
||||
[JsonInclude]
|
||||
[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
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly bool MatchEverything;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal bool MatchEverything { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly byte MaxTradeHoldDuration;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal byte MaxTradeHoldDuration { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
internal readonly string? Nickname;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
internal string? Nickname { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly uint TotalInventoryCount;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal uint TotalGamesCount { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly string TradeToken = "";
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal uint TotalInventoryCount { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal string TradeToken { get; private init; } = "";
|
||||
|
||||
[JsonConstructor]
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,8 +23,11 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
|
||||
@@ -33,8 +36,6 @@ using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Exchange;
|
||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
@@ -43,10 +44,12 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotTradeOfferResults, IBotUserNotifications {
|
||||
internal static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(ItemsMatcherPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
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) {
|
||||
@@ -56,9 +59,7 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(message);
|
||||
|
||||
if ((args == null) || (args.Length == 0)) {
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
@@ -85,7 +86,7 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I
|
||||
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);
|
||||
|
||||
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns="" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</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>
|
||||
@@ -66,4 +66,8 @@
|
||||
<value>{0} sets ont été matché pendant ce round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</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>
|
||||
|
||||
@@ -66,4 +66,16 @@
|
||||
<value>Abbinati un totale di {0} set questo round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</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>
|
||||
|
||||
@@ -66,4 +66,20 @@
|
||||
<value>Celkovo porovnaných {0} sad karet.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,16 +6,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -27,9 +27,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
@@ -45,9 +45,7 @@ internal static class Commands {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(message);
|
||||
|
||||
if ((args == null) || (args.Length == 0)) {
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
@@ -95,10 +93,7 @@ internal static class Commands {
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (string.IsNullOrEmpty(activationCode)) {
|
||||
throw new ArgumentNullException(nameof(activationCode));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(activationCode);
|
||||
|
||||
if (access < EAccess.Master) {
|
||||
return access > EAccess.None ? bot.Commands.FormatBotResponse(Strings.ErrorAccessDenied) : null;
|
||||
@@ -133,7 +128,7 @@ internal static class Commands {
|
||||
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) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
@@ -162,8 +157,7 @@ internal static class Commands {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticator.GenerateTokenForTime)));
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code!, steamTime).ConfigureAwait(false);
|
||||
CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code, steamTime).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticatorHandler.FinalizeAuthenticator)));
|
||||
@@ -211,13 +205,8 @@ internal static class Commands {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botNames)) {
|
||||
throw new ArgumentNullException(nameof(botNames));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(activationCode)) {
|
||||
throw new ArgumentNullException(nameof(activationCode));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(botNames);
|
||||
ArgumentException.ThrowIfNullOrEmpty(activationCode);
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -231,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);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -272,7 +261,7 @@ internal static class Commands {
|
||||
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) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
@@ -310,9 +299,7 @@ internal static class Commands {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botNames)) {
|
||||
throw new ArgumentNullException(nameof(botNames));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(botNames);
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -326,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);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -370,10 +357,10 @@ internal static class Commands {
|
||||
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 json = JsonConvert.SerializeObject(maFileData, Formatting.Indented);
|
||||
string json = maFileData.ToJsonText(true);
|
||||
|
||||
try {
|
||||
await File.WriteAllTextAsync(maFilePendingPath, json).ConfigureAwait(false);
|
||||
@@ -391,9 +378,7 @@ internal static class Commands {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botNames)) {
|
||||
throw new ArgumentNullException(nameof(botNames));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(botNames);
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -407,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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns="" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,52 +20,81 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
|
||||
internal sealed class MaFileData {
|
||||
[JsonProperty("account_name", Required = Required.Always)]
|
||||
internal readonly string AccountName;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("account_name")]
|
||||
[JsonRequired]
|
||||
internal string AccountName { get; private init; }
|
||||
|
||||
[JsonProperty("device_id", Required = Required.Always)]
|
||||
internal readonly string DeviceID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("device_id")]
|
||||
[JsonRequired]
|
||||
internal string DeviceID { get; private init; }
|
||||
|
||||
[JsonProperty("identity_secret", Required = Required.Always)]
|
||||
internal readonly string IdentitySecret;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("identity_secret")]
|
||||
[JsonRequired]
|
||||
internal string IdentitySecret { get; private init; }
|
||||
|
||||
[JsonProperty("revocation_code", Required = Required.Always)]
|
||||
internal readonly string RevocationCode;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("revocation_code")]
|
||||
[JsonRequired]
|
||||
internal string RevocationCode { get; private init; }
|
||||
|
||||
[JsonProperty("secret_1", Required = Required.Always)]
|
||||
internal readonly string Secret1;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("secret_1")]
|
||||
[JsonRequired]
|
||||
internal string Secret1 { get; private init; }
|
||||
|
||||
[JsonProperty("serial_number", Required = Required.Always)]
|
||||
internal readonly ulong SerialNumber;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("serial_number")]
|
||||
[JsonRequired]
|
||||
internal ulong SerialNumber { get; private init; }
|
||||
|
||||
[JsonProperty("server_time", Required = Required.Always)]
|
||||
internal readonly ulong ServerTime;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("server_time")]
|
||||
[JsonRequired]
|
||||
internal ulong ServerTime { get; private init; }
|
||||
|
||||
[JsonProperty("shared_secret", Required = Required.Always)]
|
||||
internal readonly string SharedSecret;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal MaFileSessionData Session { get; private init; }
|
||||
|
||||
[JsonProperty("status", Required = Required.Always)]
|
||||
internal readonly int Status;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("shared_secret")]
|
||||
[JsonRequired]
|
||||
internal string SharedSecret { get; private init; }
|
||||
|
||||
[JsonProperty("token_gid", Required = Required.Always)]
|
||||
internal readonly string TokenGid;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("status")]
|
||||
[JsonRequired]
|
||||
internal int Status { get; private init; }
|
||||
|
||||
[JsonProperty("uri", Required = Required.Always)]
|
||||
internal readonly string Uri;
|
||||
[JsonInclude]
|
||||
[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);
|
||||
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
throw new ArgumentNullException(nameof(deviceID));
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(deviceID);
|
||||
|
||||
AccountName = data.account_name;
|
||||
DeviceID = deviceID;
|
||||
IdentitySecret = Convert.ToBase64String(data.identity_secret);
|
||||
@@ -73,6 +102,7 @@ internal sealed class MaFileData {
|
||||
Secret1 = Convert.ToBase64String(data.secret_1);
|
||||
SerialNumber = data.serial_number;
|
||||
ServerTime = data.server_time;
|
||||
Session = new MaFileSessionData(steamID);
|
||||
SharedSecret = Convert.ToBase64String(data.shared_secret);
|
||||
Status = data.status;
|
||||
TokenGid = data.token_gid;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,24 +20,21 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
|
||||
internal sealed class WalletInfoUpdateCallback : CallbackMsg {
|
||||
internal readonly long Balance;
|
||||
internal readonly long BalanceDelayed;
|
||||
internal readonly ECurrencyCode Currency;
|
||||
internal sealed class MaFileSessionData {
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal WalletInfoUpdateCallback(JobID jobID, CMsgClientWalletInfoUpdate msg) {
|
||||
ArgumentNullException.ThrowIfNull(jobID);
|
||||
ArgumentNullException.ThrowIfNull(msg);
|
||||
internal MaFileSessionData(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
JobID = jobID;
|
||||
|
||||
Balance = msg.balance64;
|
||||
BalanceDelayed = msg.balance64_delayed;
|
||||
Currency = (ECurrencyCode) msg.currency;
|
||||
SteamID = steamID;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -33,9 +33,10 @@ internal sealed class MobileAuthenticatorHandler : ClientMsgHandler {
|
||||
private readonly SteamUnifiedMessages.UnifiedService<ITwoFactor> UnifiedTwoFactorService;
|
||||
|
||||
internal MobileAuthenticatorHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnifiedMessages) {
|
||||
ArgumentNullException.ThrowIfNull(archiLogger);
|
||||
ArgumentNullException.ThrowIfNull(steamUnifiedMessages);
|
||||
|
||||
ArchiLogger = archiLogger ?? throw new ArgumentNullException(nameof(archiLogger));
|
||||
ArchiLogger = archiLogger;
|
||||
UnifiedTwoFactorService = steamUnifiedMessages.CreateService<ITwoFactor>();
|
||||
}
|
||||
|
||||
@@ -46,9 +47,7 @@ internal sealed class MobileAuthenticatorHandler : ClientMsgHandler {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
throw new ArgumentNullException(nameof(deviceID));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(deviceID);
|
||||
|
||||
if (Client == null) {
|
||||
throw new InvalidOperationException(nameof(Client));
|
||||
@@ -90,17 +89,9 @@ internal sealed class MobileAuthenticatorHandler : ClientMsgHandler {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(activationCode)) {
|
||||
throw new ArgumentNullException(nameof(activationCode));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(authenticatorCode)) {
|
||||
throw new ArgumentNullException(nameof(authenticatorCode));
|
||||
}
|
||||
|
||||
if (authenticatorTime <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(authenticatorTime));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(activationCode);
|
||||
ArgumentException.ThrowIfNullOrEmpty(authenticatorCode);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(authenticatorTime);
|
||||
|
||||
if (Client == null) {
|
||||
throw new InvalidOperationException(nameof(Client));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,15 +22,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.MobileAuthenticator.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
@@ -38,10 +39,12 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
[Export(typeof(IPlugin))]
|
||||
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
|
||||
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient {
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(MobileAuthenticatorPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
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) {
|
||||
@@ -51,9 +54,7 @@ internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2,
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(message);
|
||||
|
||||
if ((args == null) || (args.Length == 0)) {
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
@@ -6,17 +6,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,34 +23,44 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Core;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
internal sealed class SubmitRequest {
|
||||
[JsonProperty("guid", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("guid")]
|
||||
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
|
||||
|
||||
[JsonProperty("token", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("token")]
|
||||
private static string Token => SharedInfo.Token;
|
||||
|
||||
[JsonProperty("v", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("v")]
|
||||
private static byte Version => SharedInfo.ApiVersion;
|
||||
|
||||
[JsonProperty("apps", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Apps;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("apps")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Apps { get; init; }
|
||||
|
||||
[JsonProperty("depots", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Depots;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("depots")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Depots { get; init; }
|
||||
|
||||
private readonly ulong SteamID;
|
||||
|
||||
[JsonProperty("subs", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Subs;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("subs")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Subs { get; init; }
|
||||
|
||||
[JsonProperty("steamid", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("steamid")]
|
||||
private string SteamIDText => new SteamID(SteamID).Render();
|
||||
|
||||
internal SubmitRequest(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,22 +20,21 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class SubmitResponse {
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("data", Required = Required.DisallowNull)]
|
||||
internal readonly SubmitResponseData? Data;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("data")]
|
||||
internal SubmitResponseData? Data { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("success", Required = Required.Always)]
|
||||
internal readonly bool Success;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("success")]
|
||||
[JsonRequired]
|
||||
internal bool Success { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private SubmitResponse() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,28 +20,40 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
internal sealed class SubmitResponseData {
|
||||
[JsonProperty("new_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("new_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("new_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_subs")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,51 +21,57 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
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");
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
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)]
|
||||
[JsonInclude]
|
||||
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;
|
||||
|
||||
[UsedImplicitly]
|
||||
@@ -80,9 +86,6 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeLastChangeNumber() => LastChangeNumber > 0;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializePackageTokens() => !PackageTokens.IsEmpty;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSubmittedApps() => !SubmittedApps.IsEmpty;
|
||||
|
||||
@@ -92,11 +95,20 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSubmittedPackages() => !SubmittedPackages.IsEmpty;
|
||||
|
||||
protected override Task Save() => Save(this);
|
||||
|
||||
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, 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, 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> 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) != 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() {
|
||||
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() {
|
||||
if (!File.Exists(SharedFilePath)) {
|
||||
@@ -116,7 +128,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
return null;
|
||||
}
|
||||
|
||||
globalCache = JsonConvert.DeserializeObject<GlobalCache>(json);
|
||||
globalCache = json.ToJsonObject<GlobalCache>();
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -141,10 +153,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
}
|
||||
|
||||
internal void OnPICSChanges(uint currentChangeNumber, IReadOnlyCollection<KeyValuePair<uint, SteamApps.PICSChangesCallback.PICSChangeData>> appChanges) {
|
||||
if (currentChangeNumber == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
||||
ArgumentNullException.ThrowIfNull(appChanges);
|
||||
|
||||
if (currentChangeNumber <= LastChangeNumber) {
|
||||
@@ -165,9 +174,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
}
|
||||
|
||||
internal void OnPICSChangesRestart(uint currentChangeNumber) {
|
||||
if (currentChangeNumber == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
||||
|
||||
if (currentChangeNumber <= LastChangeNumber) {
|
||||
return;
|
||||
@@ -184,7 +191,6 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
if (clear) {
|
||||
AppTokens.Clear();
|
||||
DepotKeys.Clear();
|
||||
PackageTokens.Clear();
|
||||
}
|
||||
|
||||
Utilities.InBackground(Save);
|
||||
@@ -265,25 +271,6 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
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) {
|
||||
ArgumentNullException.ThrowIfNull(apps);
|
||||
ArgumentNullException.ThrowIfNull(packages);
|
||||
@@ -324,21 +311,19 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
}
|
||||
|
||||
private static bool IsValidDepotKey(string depotKey) {
|
||||
if (string.IsNullOrEmpty(depotKey)) {
|
||||
throw new ArgumentNullException(nameof(depotKey));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(depotKey);
|
||||
|
||||
return (depotKey.Length == 64) && Utilities.IsValidHexadecimalText(depotKey);
|
||||
}
|
||||
|
||||
private static async Task<(bool Success, ImmutableHashSet<uint>? Result)> ResolveKnownDepotIDs() {
|
||||
private static async Task<(bool Success, FrozenSet<uint>? Result)> ResolveKnownDepotIDs(CancellationToken cancellationToken = default) {
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
Uri request = new($"{SharedInfo.ServerURL}/knowndepots.csv");
|
||||
|
||||
StreamResponse? response = await ASF.WebBrowser.UrlGetToStream(request).ConfigureAwait(false);
|
||||
StreamResponse? response = await ASF.WebBrowser.UrlGetToStream(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response?.Content == null) {
|
||||
return (false, null);
|
||||
@@ -348,19 +333,19 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
try {
|
||||
using StreamReader reader = new(response.Content);
|
||||
|
||||
string? countText = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
string? countText = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(countText) || !int.TryParse(countText, out int count) || (count <= 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(countText));
|
||||
ASF.ArchiLogger.LogNullError(countText);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
HashSet<uint> result = new(count);
|
||||
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { Length: > 0 } line) {
|
||||
while (await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false) is { Length: > 0 } line) {
|
||||
if (!uint.TryParse(line, out uint depotID) || (depotID == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(depotID));
|
||||
ASF.ArchiLogger.LogNullError(depotID);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -368,7 +353,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
result.Add(depotID);
|
||||
}
|
||||
|
||||
return (result.Count > 0, result.ToImmutableHashSet());
|
||||
return (result.Count > 0, result.ToFrozenSet());
|
||||
} catch (Exception 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,16 +19,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
public sealed class GlobalConfigExtension {
|
||||
[JsonProperty]
|
||||
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private set; }
|
||||
[JsonInclude]
|
||||
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SteamTokenDumperPluginEnabled { get; private set; }
|
||||
[JsonInclude]
|
||||
public bool SteamTokenDumperPluginEnabled { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
internal GlobalConfigExtension() { }
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns="" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||
<value>Nova aplikacija: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
|
||||
<value>Verifikovana aplikacija: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
|
||||
<value>Novi paketi: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
|
||||
<value>Verifikovani paketi: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -67,11 +67,11 @@
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
|
||||
|
||||
@@ -109,7 +109,10 @@
|
||||
<value>Récupération de {0} infos d'application terminée.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Récupération réussie de {0} sur {1} clés de dépôts.</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>Fin de la récupération de toutes les clés de dépôts pour un total de {0} applications.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
|
||||
@@ -109,7 +109,10 @@
|
||||
<value>Hai completato il recupero di {0} informazioni app.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Recuperate {0} chiavi depot su {1} con successo.</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>Finito il recupero di tutte le chiavi del deposito per un totale di {0} applicazioni.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
|
||||
@@ -97,7 +97,10 @@
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -106,13 +109,33 @@
|
||||
<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>
|
||||
</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">
|
||||
<value>Jaunas aplikācijas: {0}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</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>
|
||||
|
||||
@@ -109,7 +109,10 @@
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -161,7 +164,13 @@
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,29 +20,33 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.IPC.Integration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
public sealed class SteamTokenDumperConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonInclude]
|
||||
public bool Enabled { get; internal set; }
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[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)]
|
||||
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)]
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SkipAutoGrantPackages { get; private set; } = true;
|
||||
[JsonInclude]
|
||||
public bool SkipAutoGrantPackages { get; private init; } = true;
|
||||
|
||||
[JsonConstructor]
|
||||
internal SteamTokenDumperConfig() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -29,7 +29,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
[Route("Api/SteamTokenDumperPlugin")]
|
||||
public sealed class SteamTokenDumperController : ArchiController {
|
||||
[HttpGet(nameof(GlobalConfigExtension))]
|
||||
[ProducesResponseType(typeof(GlobalConfigExtension), (int) HttpStatusCode.OK)]
|
||||
[SwaggerOperation(Tags = new[] { nameof(GlobalConfigExtension) })]
|
||||
[ProducesResponseType<GlobalConfigExtension>((int) HttpStatusCode.OK)]
|
||||
[SwaggerOperation(Tags = [nameof(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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,27 +21,30 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Interaction;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
@@ -50,26 +53,28 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges {
|
||||
private const ushort DepotsRateLimitingDelay = 500;
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
internal static SteamTokenDumperConfig? Config { get; private set; }
|
||||
|
||||
private static readonly ConcurrentDictionary<Bot, IDisposable> BotSubscriptions = new();
|
||||
private static readonly ConcurrentDictionary<Bot, (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer)> BotSynchronizations = new();
|
||||
private static readonly SemaphoreSlim SubmissionSemaphore = new(1, 1);
|
||||
private static readonly Timer SubmissionTimer = new(SubmitData);
|
||||
private static readonly Timer SubmissionTimer = new(OnSubmissionTimer);
|
||||
|
||||
private static GlobalCache? GlobalCache;
|
||||
private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue;
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(SteamTokenDumperPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
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) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledMissingBuildToken, nameof(SteamTokenDumperPlugin)));
|
||||
|
||||
@@ -80,15 +85,19 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
SteamTokenDumperConfig? config = null;
|
||||
|
||||
if (additionalConfigProperties != null) {
|
||||
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
|
||||
foreach ((string configProperty, JsonElement configValue) in additionalConfigProperties) {
|
||||
try {
|
||||
switch (configProperty) {
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPlugin):
|
||||
config = configValue.ToObject<SteamTokenDumperConfig>();
|
||||
config = configValue.ToJsonObject<SteamTokenDumperConfig>();
|
||||
|
||||
break;
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled):
|
||||
isEnabled = configValue.Value<bool>();
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.False:
|
||||
isEnabled = false;
|
||||
|
||||
break;
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.True:
|
||||
isEnabled = true;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -101,30 +110,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) {
|
||||
GlobalCache? globalCache = await GlobalCache.Load().ConfigureAwait(false);
|
||||
|
||||
@@ -137,8 +122,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;
|
||||
|
||||
if (!config.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
#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));
|
||||
#pragma warning restore CA5394 // This call isn't used in a security-sensitive manner
|
||||
@@ -162,29 +179,24 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
switch (args[0].ToUpperInvariant()) {
|
||||
case "STD" when access >= EAccess.Owner:
|
||||
if (Config is not { Enabled: true }) {
|
||||
return Task.FromResult((string?) string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, nameof(Config)));
|
||||
switch (args.Length) {
|
||||
case 1:
|
||||
switch (args[0].ToUpperInvariant()) {
|
||||
case "STD":
|
||||
return Task.FromResult(ResponseRefreshManually(access, bot));
|
||||
}
|
||||
|
||||
TimeSpan minimumTimeBetweenUpload = TimeSpan.FromMinutes(SharedInfo.MinimumMinutesBetweenUploads);
|
||||
|
||||
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);
|
||||
break;
|
||||
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) {
|
||||
@@ -207,7 +219,8 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
public async Task OnBotInit(Bot 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;
|
||||
}
|
||||
|
||||
@@ -251,40 +264,19 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
}
|
||||
|
||||
public Task OnPICSChanges(uint currentChangeNumber, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> appChanges, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> packageChanges) {
|
||||
if (currentChangeNumber == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
||||
ArgumentNullException.ThrowIfNull(appChanges);
|
||||
ArgumentNullException.ThrowIfNull(packageChanges);
|
||||
|
||||
if (Config is not { Enabled: true }) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (GlobalCache == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalCache));
|
||||
}
|
||||
|
||||
GlobalCache.OnPICSChanges(currentChangeNumber, appChanges);
|
||||
GlobalCache?.OnPICSChanges(currentChangeNumber, appChanges);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnPICSChangesRestart(uint currentChangeNumber) {
|
||||
if (currentChangeNumber == 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(currentChangeNumber);
|
||||
|
||||
if (Config is not { Enabled: true }) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (GlobalCache == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalCache));
|
||||
}
|
||||
|
||||
GlobalCache.OnPICSChangesRestart(currentChangeNumber);
|
||||
GlobalCache?.OnPICSChangesRestart(currentChangeNumber);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -305,30 +297,34 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
if (GlobalCache == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalCache));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
GlobalCache.UpdatePackageTokens(packageTokens);
|
||||
|
||||
await Refresh(bot, packageTokens.Keys).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task Refresh(Bot bot, IReadOnlyCollection<uint>? packageIDs = null) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (Config is not { Enabled: true }) {
|
||||
// Schedule a refresh in a while from now
|
||||
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await synchronization.RefreshSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
// Another refresh is in progress, skip the refresh for now
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
synchronization.RefreshTimer.Change(TimeSpan.FromMinutes(1), TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh));
|
||||
} finally {
|
||||
synchronization.RefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static async void OnSubmissionTimer(object? state = null) => await SubmitData().ConfigureAwait(false);
|
||||
|
||||
private static async Task Refresh(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (GlobalCache == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalCache));
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -346,17 +342,17 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
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)) {
|
||||
// ASF might not have the package info for us at the moment, we'll retry later
|
||||
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) {
|
||||
@@ -408,7 +404,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.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()) {
|
||||
while (true) {
|
||||
@@ -461,7 +457,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
bool shouldFetchMainKey = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -524,7 +520,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) {
|
||||
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
|
||||
@@ -537,9 +535,11 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalDepots, appIDsToRefresh.Count));
|
||||
} 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);
|
||||
|
||||
@@ -549,13 +549,72 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
}
|
||||
}
|
||||
|
||||
private static async void SubmitData(object? state = null) {
|
||||
if (Bot.Bots == null) {
|
||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||
private static string? ResponseRefreshManually(EAccess access, Bot bot) {
|
||||
if (!Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if (Config is not { Enabled: true }) {
|
||||
return;
|
||||
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) {
|
||||
if (Bot.Bots == null) {
|
||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||
}
|
||||
|
||||
if (GlobalCache == null) {
|
||||
@@ -570,7 +629,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await SubmissionSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
if (!await SubmissionSemaphore.WaitAsync(0, cancellationToken).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -598,7 +657,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionInProgress, appTokens.Count, packageTokens.Count, depotKeys.Count));
|
||||
|
||||
ObjectResponse<SubmitResponse>? response = await ASF.WebBrowser.UrlPostToJsonObject<SubmitResponse, SubmitRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
ObjectResponse<SubmitResponse>? response = await ASF.WebBrowser.UrlPostToJsonObject<SubmitResponse, SubmitRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed);
|
||||
@@ -613,7 +672,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.StatusCode));
|
||||
|
||||
switch (response.StatusCode) {
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.Forbidden when Config?.Enabled == true:
|
||||
// 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
|
||||
lock (SubmissionSemaphore) {
|
||||
@@ -626,12 +685,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
GlobalCache.Reset(true);
|
||||
|
||||
break;
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
case (HttpStatusCode) 429:
|
||||
#else
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
#endif
|
||||
|
||||
case HttpStatusCode.TooManyRequests when Config?.Enabled == true:
|
||||
// SteamDB told us to try again later
|
||||
#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));
|
||||
@@ -657,7 +711,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="MSTest.TestAdapter" />
|
||||
<PackageReference Include="MSTest.TestFramework" />
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -39,7 +39,7 @@ public sealed class Bot {
|
||||
{ 43, MinCardsPerBadge + 1 }
|
||||
};
|
||||
|
||||
HashSet<Asset> items = new();
|
||||
HashSet<Asset> items = [];
|
||||
|
||||
foreach ((uint appID, byte cards) in itemsPerSet) {
|
||||
for (byte i = 1; i <= cards; i++) {
|
||||
@@ -55,30 +55,27 @@ public sealed class Bot {
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void MaxItemsTooSmall() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
};
|
||||
];
|
||||
|
||||
GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1);
|
||||
|
||||
Assert.Fail();
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MoreCardsThanNeeded() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(3, appID)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
@@ -95,12 +92,12 @@ public sealed class Bot {
|
||||
public void MultipleSets() {
|
||||
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);
|
||||
|
||||
@@ -116,11 +113,11 @@ public sealed class Bot {
|
||||
public void MultipleSetsDifferentAmount() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, 2),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(2, appID)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
@@ -136,7 +133,7 @@ public sealed class Bot {
|
||||
public void MutliRarityAndType() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, 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(3, 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);
|
||||
|
||||
@@ -177,10 +174,10 @@ public sealed class Bot {
|
||||
public void NotAllCardsPresent() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
@@ -192,10 +189,10 @@ public sealed class Bot {
|
||||
public void OneSet() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
@@ -212,10 +209,10 @@ public sealed class Bot {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
@@ -237,10 +234,10 @@ public sealed class Bot {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
@@ -260,14 +257,14 @@ public sealed class Bot {
|
||||
const uint appID1 = 43;
|
||||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(2, appID0),
|
||||
|
||||
CreateCard(1, appID1),
|
||||
CreateCard(2, appID1),
|
||||
CreateCard(3, appID1)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
@@ -290,10 +287,10 @@ public sealed class Bot {
|
||||
public void OtherRarityFullSets() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
|
||||
@@ -308,10 +305,10 @@ public sealed class Bot {
|
||||
public void OtherRarityNoSets() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
@@ -324,13 +321,13 @@ public sealed class Bot {
|
||||
public void OtherRarityOneSet() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(2, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
@@ -347,10 +344,10 @@ public sealed class Bot {
|
||||
public void OtherTypeFullSets() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
|
||||
@@ -365,10 +362,10 @@ public sealed class Bot {
|
||||
public void OtherTypeNoSets() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
@@ -381,13 +378,13 @@ public sealed class Bot {
|
||||
public void OtherTypeOneSet() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(2, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
|
||||
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
|
||||
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
|
||||
@@ -404,10 +401,10 @@ public sealed class Bot {
|
||||
public void TooHighAmount() {
|
||||
const uint appID0 = 42;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0, 2),
|
||||
CreateCard(2, appID0)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
|
||||
|
||||
@@ -423,7 +420,7 @@ public sealed class Bot {
|
||||
public void TooManyCardsForSingleTrade() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = new();
|
||||
HashSet<Asset> items = [];
|
||||
|
||||
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
|
||||
items.Add(CreateCard(1, appID));
|
||||
@@ -440,7 +437,7 @@ public sealed class Bot {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = new();
|
||||
HashSet<Asset> items = [];
|
||||
|
||||
for (byte i = 0; i < 100; i++) {
|
||||
items.Add(CreateCard(1, appID0));
|
||||
@@ -460,28 +457,27 @@ public sealed class Bot {
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void TooManyCardsPerSet() {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> items = new() {
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(2, appID0),
|
||||
CreateCard(3, appID0),
|
||||
CreateCard(4, appID0)
|
||||
};
|
||||
];
|
||||
|
||||
GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
{ appID0, 3 },
|
||||
{ appID1, 3 },
|
||||
{ appID2, 3 }
|
||||
}
|
||||
Assert.ThrowsException<InvalidOperationException>(
|
||||
() => GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
{ appID0, 3 },
|
||||
{ appID1, 3 },
|
||||
{ appID2, 3 }
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -200,60 +200,62 @@ public sealed class SteamChatMessage {
|
||||
public async Task RyzhehvostInitialTestForSplitting() {
|
||||
const string prefix = "/me ";
|
||||
|
||||
const string message = @"<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
|
||||
<XLimited5> Уже имеет: app/349520 | Armillo
|
||||
<XLimited5> Уже имеет: app/346330 | BrainBread 2
|
||||
<XLimited5> Уже имеет: app/1086690 | C-War 2
|
||||
<XLimited5> Уже имеет: app/730 | Counter-Strike: Global Offensive
|
||||
<XLimited5> Уже имеет: app/838380 | DEAD OR ALIVE 6
|
||||
<XLimited5> Уже имеет: app/582890 | Estranged: The Departure
|
||||
<XLimited5> Уже имеет: app/331470 | Everlasting Summer
|
||||
<XLimited5> Уже имеет: app/1078000 | Gamecraft
|
||||
<XLimited5> Уже имеет: app/266310 | GameGuru
|
||||
<XLimited5> Уже имеет: app/275390 | Guacamelee! Super Turbo Championship Edition
|
||||
<XLimited5> Уже имеет: app/627690 | Idle Champions of the Forgotten Realms
|
||||
<XLimited5> Уже имеет: app/1048540 | Kao the Kangaroo: Round 2
|
||||
<XLimited5> Уже имеет: app/370910 | Kathy Rain
|
||||
<XLimited5> Уже имеет: app/343710 | KHOLAT
|
||||
<XLimited5> Уже имеет: app/253900 | Knights and Merchants
|
||||
<XLimited5> Уже имеет: app/224260 | No More Room in Hell
|
||||
<XLimited5> Уже имеет: app/343360 | Particula
|
||||
<XLimited5> Уже имеет: app/237870 | Planet Explorers
|
||||
<XLimited5> Уже имеет: app/684680 | Polygoneer
|
||||
<XLimited5> Уже имеет: app/1089130 | Quake II RTX
|
||||
<XLimited5> Уже имеет: app/755790 | Ring of Elysium
|
||||
<XLimited5> Уже имеет: app/1258080 | Shop Titans
|
||||
<XLimited5> Уже имеет: app/759530 | Struckd - 3D Game Creator
|
||||
<XLimited5> Уже имеет: app/269710 | Tumblestone
|
||||
<XLimited5> Уже имеет: app/304930 | Unturned
|
||||
<XLimited5> Уже имеет: app/1019250 | WWII TCG - World War 2: The Card Game
|
||||
const string message = """
|
||||
<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
|
||||
<XLimited5> Уже имеет: app/349520 | Armillo
|
||||
<XLimited5> Уже имеет: app/346330 | BrainBread 2
|
||||
<XLimited5> Уже имеет: app/1086690 | C-War 2
|
||||
<XLimited5> Уже имеет: app/730 | Counter-Strike: Global Offensive
|
||||
<XLimited5> Уже имеет: app/838380 | DEAD OR ALIVE 6
|
||||
<XLimited5> Уже имеет: app/582890 | Estranged: The Departure
|
||||
<XLimited5> Уже имеет: app/331470 | Everlasting Summer
|
||||
<XLimited5> Уже имеет: app/1078000 | Gamecraft
|
||||
<XLimited5> Уже имеет: app/266310 | GameGuru
|
||||
<XLimited5> Уже имеет: app/275390 | Guacamelee! Super Turbo Championship Edition
|
||||
<XLimited5> Уже имеет: app/627690 | Idle Champions of the Forgotten Realms
|
||||
<XLimited5> Уже имеет: app/1048540 | Kao the Kangaroo: Round 2
|
||||
<XLimited5> Уже имеет: app/370910 | Kathy Rain
|
||||
<XLimited5> Уже имеет: app/343710 | KHOLAT
|
||||
<XLimited5> Уже имеет: app/253900 | Knights and Merchants
|
||||
<XLimited5> Уже имеет: app/224260 | No More Room in Hell
|
||||
<XLimited5> Уже имеет: app/343360 | Particula
|
||||
<XLimited5> Уже имеет: app/237870 | Planet Explorers
|
||||
<XLimited5> Уже имеет: app/684680 | Polygoneer
|
||||
<XLimited5> Уже имеет: app/1089130 | Quake II RTX
|
||||
<XLimited5> Уже имеет: app/755790 | Ring of Elysium
|
||||
<XLimited5> Уже имеет: app/1258080 | Shop Titans
|
||||
<XLimited5> Уже имеет: app/759530 | Struckd - 3D Game Creator
|
||||
<XLimited5> Уже имеет: app/269710 | Tumblestone
|
||||
<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/349520 | Armillo.
|
||||
<ASF> 1/1 ботов уже имеют игру app/346330 | BrainBread 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1086690 | C-War 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/730 | Counter-Strike: Global Offensive.
|
||||
<ASF> 1/1 ботов уже имеют игру app/838380 | DEAD OR ALIVE 6.
|
||||
<ASF> 1/1 ботов уже имеют игру app/582890 | Estranged: The Departure.
|
||||
<ASF> 1/1 ботов уже имеют игру app/331470 | Everlasting Summer.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1078000 | Gamecraft.
|
||||
<ASF> 1/1 ботов уже имеют игру app/266310 | GameGuru.
|
||||
<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/1048540 | Kao the Kangaroo: Round 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/370910 | Kathy Rain.
|
||||
<ASF> 1/1 ботов уже имеют игру app/343710 | KHOLAT.
|
||||
<ASF> 1/1 ботов уже имеют игру app/253900 | Knights and Merchants.
|
||||
<ASF> 1/1 ботов уже имеют игру app/224260 | No More Room in Hell.
|
||||
<ASF> 1/1 ботов уже имеют игру app/343360 | Particula.
|
||||
<ASF> 1/1 ботов уже имеют игру app/237870 | Planet Explorers.
|
||||
<ASF> 1/1 ботов уже имеют игру app/684680 | Polygoneer.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1089130 | Quake II RTX.
|
||||
<ASF> 1/1 ботов уже имеют игру app/755790 | Ring of Elysium.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1258080 | Shop Titans.
|
||||
<ASF> 1/1 ботов уже имеют игру app/759530 | Struckd - 3D Game Creator.
|
||||
<ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
|
||||
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.";
|
||||
<ASF> 1/1 ботов уже имеют игру app/1493800 | Aircraft Carrier Survival: Prolouge.
|
||||
<ASF> 1/1 ботов уже имеют игру app/349520 | Armillo.
|
||||
<ASF> 1/1 ботов уже имеют игру app/346330 | BrainBread 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1086690 | C-War 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/730 | Counter-Strike: Global Offensive.
|
||||
<ASF> 1/1 ботов уже имеют игру app/838380 | DEAD OR ALIVE 6.
|
||||
<ASF> 1/1 ботов уже имеют игру app/582890 | Estranged: The Departure.
|
||||
<ASF> 1/1 ботов уже имеют игру app/331470 | Everlasting Summer.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1078000 | Gamecraft.
|
||||
<ASF> 1/1 ботов уже имеют игру app/266310 | GameGuru.
|
||||
<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/1048540 | Kao the Kangaroo: Round 2.
|
||||
<ASF> 1/1 ботов уже имеют игру app/370910 | Kathy Rain.
|
||||
<ASF> 1/1 ботов уже имеют игру app/343710 | KHOLAT.
|
||||
<ASF> 1/1 ботов уже имеют игру app/253900 | Knights and Merchants.
|
||||
<ASF> 1/1 ботов уже имеют игру app/224260 | No More Room in Hell.
|
||||
<ASF> 1/1 ботов уже имеют игру app/343360 | Particula.
|
||||
<ASF> 1/1 ботов уже имеют игру app/237870 | Planet Explorers.
|
||||
<ASF> 1/1 ботов уже имеют игру app/684680 | Polygoneer.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1089130 | Quake II RTX.
|
||||
<ASF> 1/1 ботов уже имеют игру app/755790 | Ring of Elysium.
|
||||
<ASF> 1/1 ботов уже имеют игру app/1258080 | Shop Titans.
|
||||
<ASF> 1/1 ботов уже имеют игру app/759530 | Struckd - 3D Game Creator.
|
||||
<ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
|
||||
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.
|
||||
""";
|
||||
|
||||
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -266,7 +268,7 @@ public sealed class SteamChatMessage {
|
||||
return;
|
||||
}
|
||||
|
||||
string[] lines = messagePart.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||
string[] lines = messagePart.Split(SharedInfo.NewLineIndicators, StringSplitOptions.None);
|
||||
|
||||
int bytes = lines.Where(static line => line.Length > 0).Sum(Encoding.UTF8.GetByteCount) + ((lines.Length - 1) * NewlineWeight);
|
||||
|
||||
@@ -309,27 +311,21 @@ public sealed class SteamChatMessage {
|
||||
Assert.AreEqual(newlinePart, output[3]);
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
[TestMethod]
|
||||
public async Task ThrowsOnTooLongNewlinesPrefix() {
|
||||
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
|
||||
|
||||
const string message = "asdf";
|
||||
|
||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.Fail();
|
||||
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
[TestMethod]
|
||||
public async Task ThrowsOnTooLongPrefix() {
|
||||
string prefix = new('x', MaxMessagePrefixBytes + 1);
|
||||
|
||||
const string message = "asdf";
|
||||
|
||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.Fail();
|
||||
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -30,21 +30,21 @@ namespace ArchiSteamFarm.Tests;
|
||||
public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void ExploitingNewSetsIsFairButNotNeutral() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 40),
|
||||
CreateItem(2, 10),
|
||||
CreateItem(3, 10)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2, 5),
|
||||
CreateItem(3, 5)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(4)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -52,45 +52,45 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchRarityIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, rarity: Asset.ERarity.Rare) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchRealAppIDsIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, realAppID: 570) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, realAppID: 570)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchTypesIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1, type: Asset.EType.Emoticon) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultiGameMultiTypeBadReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9, 730, Asset.EType.Emoticon),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -98,20 +98,20 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void MultiGameMultiTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -119,21 +119,21 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void MultiGameSingleTypeBadReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, realAppID: 730),
|
||||
CreateItem(4, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -141,20 +141,20 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void MultiGameSingleTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(3, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, realAppID: 730)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -162,21 +162,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3),
|
||||
CreateItem(4),
|
||||
CreateItem(5)
|
||||
};
|
||||
];
|
||||
|
||||
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(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -184,18 +180,14 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameDonationAccept() {
|
||||
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() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, type: Asset.EType.SteamGems)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -203,21 +195,21 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameMultiTypeBadReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9, type: Asset.EType.Emoticon),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -225,20 +217,20 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameMultiTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -246,19 +238,19 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameQuantityBadReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2),
|
||||
CreateItem(3)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(2),
|
||||
CreateItem(3)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(4, 3) };
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(4, 3)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -266,17 +258,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameQuantityBadReject2() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 3) };
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 3)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -284,17 +276,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameQuantityNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(2)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 2) };
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 2)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -302,13 +294,13 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeBadReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -316,18 +308,18 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeBadWithOverpayingReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2, 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(3)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -335,14 +327,14 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeBigDifferenceAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 5),
|
||||
CreateItem(3)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(3) };
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -350,23 +342,23 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeBigDifferenceReject() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3, 2),
|
||||
CreateItem(4, 3),
|
||||
CreateItem(5, 10)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = new() {
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2),
|
||||
CreateItem(5)
|
||||
};
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = new() {
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(3),
|
||||
CreateItem(4)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -374,9 +366,9 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeGoodAccept() {
|
||||
HashSet<Asset> inventory = new() { CreateItem(1, 2) };
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> inventory = [CreateItem(1, 2)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -384,9 +376,9 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = new() { CreateItem(1) };
|
||||
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
|
||||
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
|
||||
HashSet<Asset> inventory = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -394,17 +386,41 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
|
||||
HashSet<Asset> inventory = new() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 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(3)
|
||||
};
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 52),
|
||||
CreateItem(2, 73),
|
||||
CreateItem(3, 52),
|
||||
CreateItem(4, 47),
|
||||
CreateItem(5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2, 73)];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9),
|
||||
CreateItem(4, 8),
|
||||
CreateItem(5, 24),
|
||||
CreateItem(6, 23)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(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
|
||||
// |
|
||||
// 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>
|
||||
<OpenApiGenerateDocuments>false</OpenApiGenerateDocuments>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
||||
<!-- TODO: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2550 -->
|
||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -13,41 +16,16 @@
|
||||
<PackageReference Include="Humanizer" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Markdig.Signed" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||
<PackageReference Include="Nito.AsyncEx.Coordination" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" />
|
||||
<PackageReference Include="SteamKit2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
|
||||
<PackageReference Include="System.Composition" />
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
<PackageReference Include="zxcvbn-core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net481'">
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481' OR '$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCaching" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<Reference Include="System.Net.Http" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.Net.Http.dll" />
|
||||
<Reference Include="System.Security" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.Security.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" />
|
||||
<PackageReference Include="zxcvbn-core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -86,7 +64,7 @@
|
||||
</Content>
|
||||
</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])/**/*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
@@ -94,7 +72,7 @@
|
||||
</Content>
|
||||
</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)/**/*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -35,9 +35,10 @@ internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
|
||||
|
||||
internal ConcurrentEnumerator(IReadOnlyCollection<T> collection, IDisposable lockObject) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
ArgumentNullException.ThrowIfNull(lockObject);
|
||||
|
||||
LockObject = lockObject ?? throw new ArgumentNullException(nameof(lockObject));
|
||||
Enumerator = collection.GetEnumerator();
|
||||
LockObject = lockObject;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -24,11 +24,13 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
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 int Count => BackingCollection.Count;
|
||||
@@ -36,15 +38,31 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
private readonly ConcurrentDictionary<T, bool> BackingCollection;
|
||||
|
||||
[JsonConstructor]
|
||||
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) {
|
||||
ArgumentNullException.ThrowIfNull(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) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
if (!BackingCollection.TryAdd(item, true)) {
|
||||
return false;
|
||||
}
|
||||
@@ -64,9 +82,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
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) {
|
||||
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 void IntersectWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
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) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return this.All(otherSet.Contains);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return otherSet.All(Contains);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return otherSet.Any(Contains);
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
if (!BackingCollection.TryRemove(item, out _)) {
|
||||
return false;
|
||||
}
|
||||
@@ -127,14 +168,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count == Count) && otherSet.All(Contains);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
HashSet<T> removed = new();
|
||||
HashSet<T> removed = [];
|
||||
|
||||
foreach (T item in otherSet.Where(Contains)) {
|
||||
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();
|
||||
|
||||
[PublicAPI]
|
||||
public bool AddRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
|
||||
foreach (T _ in items.Where(Add)) {
|
||||
@@ -171,6 +222,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
[PublicAPI]
|
||||
public bool RemoveRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
|
||||
foreach (T _ in items.Where(Remove)) {
|
||||
@@ -180,8 +233,17 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
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]
|
||||
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
if (SetEquals(other)) {
|
||||
return false;
|
||||
}
|
||||
@@ -193,6 +255,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
[PublicAPI]
|
||||
public void ReplaceWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
Clear();
|
||||
UnionWith(other);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,16 +19,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
public bool IsReadOnly => false;
|
||||
public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> where T : notnull {
|
||||
[PublicAPI]
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
internal int Count {
|
||||
[PublicAPI]
|
||||
public int Count {
|
||||
get {
|
||||
using (Lock.ReaderLock()) {
|
||||
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();
|
||||
|
||||
int ICollection<T>.Count => Count;
|
||||
@@ -50,31 +57,55 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
|
||||
set {
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
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) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Add(item);
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Clear();
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
return BackingCollection.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
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 int IndexOf(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
return BackingCollection.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, T item) {
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Insert(index, item);
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
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) {
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.RemoveAt(index);
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void ReplaceWith(IEnumerable<T> collection) {
|
||||
[PublicAPI]
|
||||
public void ReplaceWith(IEnumerable<T> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Clear();
|
||||
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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,22 +23,17 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using ArchiSteamFarm.Core;
|
||||
|
||||
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();
|
||||
|
||||
internal byte MaxCount {
|
||||
get => BackingMaxCount;
|
||||
|
||||
set {
|
||||
if (value == 0) {
|
||||
ASF.ArchiLogger.LogNullError(value);
|
||||
|
||||
return;
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(value);
|
||||
|
||||
BackingMaxCount = value;
|
||||
|
||||
@@ -49,9 +44,7 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
||||
private byte BackingMaxCount;
|
||||
|
||||
internal FixedSizeConcurrentQueue(byte maxCount) {
|
||||
if (maxCount == 0) {
|
||||
throw new ArgumentNullException(nameof(maxCount));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxCount);
|
||||
|
||||
MaxCount = maxCount;
|
||||
}
|
||||
@@ -60,6 +53,8 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void Enqueue(T obj) {
|
||||
ArgumentNullException.ThrowIfNull(obj);
|
||||
|
||||
BackingQueue.Enqueue(obj);
|
||||
|
||||
Resize();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,12 +23,13 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
||||
[PublicAPI]
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
[PublicAPI]
|
||||
@@ -42,8 +43,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
[PublicAPI]
|
||||
public ICollection<TKey> Keys => BackingDictionary.Keys;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary = new();
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary;
|
||||
|
||||
int ICollection<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] {
|
||||
get => BackingDictionary[key];
|
||||
|
||||
set {
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
||||
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) {
|
||||
(TKey key, TValue value) = item;
|
||||
|
||||
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() {
|
||||
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 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 bool Remove(KeyValuePair<TKey, TValue> item) {
|
||||
@@ -98,6 +134,8 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (!BackingDictionary.TryRemove(key, out _)) {
|
||||
return false;
|
||||
}
|
||||
@@ -107,14 +145,36 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey 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();
|
||||
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]
|
||||
public bool TryAdd(TKey key, TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (!BackingDictionary.TryAdd(key, value)) {
|
||||
return false;
|
||||
}
|
||||
@@ -125,5 +185,9 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
[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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@@ -30,7 +30,6 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
@@ -76,9 +75,9 @@ public static class ASF {
|
||||
internal static ICrossProcessSemaphore? LoginRateLimitingSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore? LoginSemaphore { 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("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 Timer? AutoUpdatesTimer;
|
||||
@@ -102,23 +101,32 @@ public static class ASF {
|
||||
return fileType switch {
|
||||
EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName),
|
||||
EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(fileType))
|
||||
EFileType.Crash => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalCrashFileName),
|
||||
_ => throw new InvalidOperationException(nameof(fileType))
|
||||
};
|
||||
}
|
||||
|
||||
internal static async Task Init() {
|
||||
internal static async Task<bool> Init() {
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (!PluginsCore.InitPlugins()) {
|
||||
await Task.Delay(SharedInfo.InformationDelay).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||
|
||||
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 InitRateLimiters().ConfigureAwait(false);
|
||||
|
||||
@@ -143,12 +151,12 @@ public static class ASF {
|
||||
if (Program.ConfigWatch) {
|
||||
InitConfigWatchEvents();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsValidBotName(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
throw new ArgumentNullException(nameof(botName));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(botName);
|
||||
|
||||
if (botName[0] == '.') {
|
||||
return false;
|
||||
@@ -270,7 +278,7 @@ public static class ASF {
|
||||
}
|
||||
|
||||
string targetFile = $"{SharedInfo.ASF}-{SharedInfo.BuildInfo.Variant}.zip";
|
||||
GitHub.ReleaseResponse.Asset? binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name!.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
|
||||
GitHub.ReleaseResponse.Asset? binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (binaryAsset == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssetForThisVersion);
|
||||
@@ -300,7 +308,7 @@ public static class ASF {
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(releaseResponse.ChangelogPlainText)) {
|
||||
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText!);
|
||||
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024));
|
||||
@@ -312,7 +320,7 @@ public static class ASF {
|
||||
BinaryResponse? response;
|
||||
|
||||
try {
|
||||
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL!, progressReporter: progressReporter).ConfigureAwait(false);
|
||||
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL, progressReporter: progressReporter).ConfigureAwait(false);
|
||||
} finally {
|
||||
progressReporter.ProgressChanged -= OnProgressChanged;
|
||||
}
|
||||
@@ -325,7 +333,7 @@ public static class ASF {
|
||||
|
||||
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)) {
|
||||
ArchiLogger.LogGenericError(Strings.ChecksumWrong);
|
||||
@@ -386,9 +394,7 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task<bool> CanHandleWriteEvent(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||
|
||||
if (LastWriteEvents == null) {
|
||||
throw new InvalidOperationException(nameof(LastWriteEvents));
|
||||
@@ -408,7 +414,6 @@ public static class ASF {
|
||||
private static HashSet<string> GetLoadedAssembliesNames() {
|
||||
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
return loadedAssemblies.Select(static loadedAssembly => loadedAssembly.FullName).Where(static name => !string.IsNullOrEmpty(name)).ToHashSet(StringComparer.Ordinal)!;
|
||||
}
|
||||
|
||||
@@ -447,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.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)) }
|
||||
}.ToImmutableDictionary();
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
private static void LoadAllAssemblies() {
|
||||
@@ -515,21 +520,14 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnChangedConfigFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
await OnCreatedConfigFile(name, fullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task OnChangedConfigFile(string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
|
||||
if (!name.Equals(SharedInfo.IPCConfigFile, StringComparison.OrdinalIgnoreCase) || (GlobalConfig?.IPC != true)) {
|
||||
return;
|
||||
@@ -545,13 +543,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnChangedFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
string extension = Path.GetExtension(name);
|
||||
|
||||
@@ -569,13 +562,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnChangedKeysFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
await OnCreatedKeysFile(name, fullPath).ConfigureAwait(false);
|
||||
}
|
||||
@@ -618,13 +606,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnCreatedConfigFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
string extension = Path.GetExtension(name);
|
||||
|
||||
@@ -641,13 +624,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnCreatedFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
string extension = Path.GetExtension(name);
|
||||
|
||||
@@ -665,13 +643,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnCreatedJsonFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
if (Bot.Bots == null) {
|
||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||
@@ -709,13 +682,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnCreatedKeysFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
if (Bot.Bots == null) {
|
||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||
@@ -754,13 +722,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnDeletedConfigFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
string extension = Path.GetExtension(name);
|
||||
|
||||
@@ -777,13 +740,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnDeletedFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
string extension = Path.GetExtension(name);
|
||||
|
||||
@@ -797,13 +755,8 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task OnDeletedJsonConfigFile(string name, string fullPath) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullPath)) {
|
||||
throw new ArgumentNullException(nameof(fullPath));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
ArgumentException.ThrowIfNullOrEmpty(fullPath);
|
||||
|
||||
if (Bot.Bots == null) {
|
||||
throw new InvalidOperationException(nameof(Bot.Bots));
|
||||
@@ -871,9 +824,48 @@ 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() {
|
||||
if ((GlobalConfig == null) || (GlobalDatabase == null) || (WebBrowser == null)) {
|
||||
throw new InvalidOperationException($"{nameof(GlobalConfig)} || {nameof(GlobalDatabase)} || {nameof(WebBrowser)}");
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (GlobalDatabase == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalDatabase));
|
||||
}
|
||||
|
||||
if (WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(WebBrowser));
|
||||
}
|
||||
|
||||
// Ensure that we ask for a list of servers if we don't have any saved servers available
|
||||
@@ -955,15 +947,15 @@ public static class ASF {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow crash file recovery, if needed
|
||||
Program.AllowCrashFileRemoval = true;
|
||||
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool UpdateFromArchive(ZipArchive archive, string targetDirectory) {
|
||||
ArgumentNullException.ThrowIfNull(archive);
|
||||
|
||||
if (string.IsNullOrEmpty(targetDirectory)) {
|
||||
throw new ArgumentNullException(nameof(targetDirectory));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(targetDirectory);
|
||||
|
||||
if (SharedInfo.HomeDirectory == AppContext.BaseDirectory) {
|
||||
// We're running a build that includes our dependencies in ASF's home
|
||||
@@ -1069,10 +1061,8 @@ public static class ASF {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
if (!Directory.Exists(directory!)) {
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
Directory.CreateDirectory(directory!);
|
||||
if (!Directory.Exists(directory)) {
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// We're not interested in extracting placeholder files (but we still want directories created for them, done above)
|
||||
@@ -1102,6 +1092,7 @@ public static class ASF {
|
||||
|
||||
internal enum EFileType : byte {
|
||||
Config,
|
||||
Database
|
||||
Database,
|
||||
Crash
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -36,7 +36,7 @@ internal static class AprilFools {
|
||||
internal static void Init(object? state = null) {
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
if (now is { Month: 4, Day: 1 }) {
|
||||
if (now is (_, 4, 1)) {
|
||||
try {
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(SharedInfo.LolcatCultureName);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -25,6 +25,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
@@ -42,12 +43,9 @@ internal static class ArchiNet {
|
||||
|
||||
private static readonly ArchiCacheable<IReadOnlyCollection<ulong>> CachedBadBots = new(ResolveCachedBadBots, TimeSpan.FromDays(1));
|
||||
|
||||
internal static async Task<string?> FetchBuildChecksum(Version version, string variant) {
|
||||
internal static async Task<string?> FetchBuildChecksum(Version version, string variant, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(version);
|
||||
|
||||
if (string.IsNullOrEmpty(variant)) {
|
||||
throw new ArgumentNullException(nameof(variant));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(variant);
|
||||
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
@@ -55,7 +53,7 @@ internal static class ArchiNet {
|
||||
|
||||
Uri request = new(URL, $"/Api/Checksum/{version}/{variant}");
|
||||
|
||||
ObjectResponse<GenericResponse<string>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<string>>(request).ConfigureAwait(false);
|
||||
ObjectResponse<GenericResponse<string>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<string>>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response?.Content == null) {
|
||||
return null;
|
||||
@@ -64,17 +62,17 @@ internal static class ArchiNet {
|
||||
return response.Content.Result ?? "";
|
||||
}
|
||||
|
||||
internal static async Task<bool?> IsBadBot(ulong steamID) {
|
||||
internal static async Task<bool?> IsBadBot(ulong steamID, CancellationToken cancellationToken = default) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
(_, IReadOnlyCollection<ulong>? badBots) = await CachedBadBots.GetValue(ECacheFallback.FailedNow).ConfigureAwait(false);
|
||||
(_, IReadOnlyCollection<ulong>? badBots) = await CachedBadBots.GetValue(ECacheFallback.FailedNow, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return badBots?.Contains(steamID);
|
||||
}
|
||||
|
||||
internal static async Task<HttpStatusCode?> SignInWithSteam(Bot bot, WebBrowser webBrowser) {
|
||||
internal static async Task<HttpStatusCode?> SignInWithSteam(Bot bot, WebBrowser webBrowser, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
@@ -85,7 +83,7 @@ internal static class ArchiNet {
|
||||
// We expect data or redirection to Steam OpenID
|
||||
Uri authenticateRequest = new(URL, $"/Api/Steam/Authenticate?steamID={bot.SteamID}");
|
||||
|
||||
ObjectResponse<GenericResponse<ulong>>? authenticateResponse = await webBrowser.UrlGetToJsonObject<GenericResponse<ulong>>(authenticateRequest, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
ObjectResponse<GenericResponse<ulong>>? authenticateResponse = await webBrowser.UrlGetToJsonObject<GenericResponse<ulong>>(authenticateRequest, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (authenticateResponse == null) {
|
||||
return null;
|
||||
@@ -100,7 +98,7 @@ internal static class ArchiNet {
|
||||
}
|
||||
|
||||
// We've got a redirection, initiate OpenID procedure by following it
|
||||
using HtmlDocumentResponse? challengeResponse = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(authenticateResponse.FinalUri).ConfigureAwait(false);
|
||||
using HtmlDocumentResponse? challengeResponse = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(authenticateResponse.FinalUri, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (challengeResponse?.Content == null) {
|
||||
return null;
|
||||
@@ -153,14 +151,14 @@ internal static class ArchiNet {
|
||||
data.Add(nonceContent, "nonce");
|
||||
|
||||
// Accept OpenID request presented and follow redirection back to the data we initially expected
|
||||
BasicResponse? loginResponse = await bot.ArchiWebHandler.WebBrowser.UrlPost(loginRequest, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections).ConfigureAwait(false);
|
||||
BasicResponse? loginResponse = await bot.ArchiWebHandler.WebBrowser.UrlPost(loginRequest, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (loginResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We've got a final redirection, follow it and complete login procedure
|
||||
authenticateResponse = await webBrowser.UrlGetToJsonObject<GenericResponse<ulong>>(loginResponse.FinalUri, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
authenticateResponse = await webBrowser.UrlGetToJsonObject<GenericResponse<ulong>>(loginResponse.FinalUri, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (authenticateResponse == null) {
|
||||
return null;
|
||||
@@ -173,7 +171,7 @@ internal static class ArchiNet {
|
||||
return authenticateResponse.Content?.Result == bot.SteamID ? HttpStatusCode.OK : HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
private static async Task<(bool Success, IReadOnlyCollection<ulong>? Result)> ResolveCachedBadBots() {
|
||||
private static async Task<(bool Success, IReadOnlyCollection<ulong>? Result)> ResolveCachedBadBots(CancellationToken cancellationToken = default) {
|
||||
if (ASF.GlobalDatabase == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
@@ -184,7 +182,7 @@ internal static class ArchiNet {
|
||||
|
||||
Uri request = new(URL, "/Api/BadBots");
|
||||
|
||||
ObjectResponse<GenericResponse<ImmutableHashSet<ulong>>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<ImmutableHashSet<ulong>>>(request).ConfigureAwait(false);
|
||||
ObjectResponse<GenericResponse<ImmutableHashSet<ulong>>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<ImmutableHashSet<ulong>>>(request, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response?.Content?.Result == null) {
|
||||
return (false, ASF.GlobalDatabase.CachedBadBots);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// 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
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -30,12 +30,6 @@ internal static partial class GeneratedRegexes {
|
||||
private const string DigitsPattern = @"\d+";
|
||||
private const string NonAsciiPattern = @"[^\u0000-\u007F]+";
|
||||
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
internal static Regex CdKey() => new(CdKeyPattern, DefaultOptions);
|
||||
internal static Regex Decimal() => new(DecimalPattern, DefaultOptions);
|
||||
internal static Regex Digits() => new(DigitsPattern, DefaultOptions);
|
||||
internal static Regex NonAscii() => new(NonAsciiPattern, DefaultOptions);
|
||||
#else
|
||||
[GeneratedRegex(CdKeyPattern, DefaultOptions)]
|
||||
internal static partial Regex CdKey();
|
||||
|
||||
@@ -47,5 +41,4 @@ internal static partial class GeneratedRegexes {
|
||||
|
||||
[GeneratedRegex(NonAsciiPattern, DefaultOptions)]
|
||||
internal static partial Regex NonAscii();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -29,67 +29,37 @@ internal static partial class NativeMethods {
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool GetConsoleMode(nint hConsoleHandle, out EConsoleMode lpMode);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial bool GetConsoleMode(nint hConsoleHandle, out EConsoleMode lpMode);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("libc", EntryPoint = "geteuid", SetLastError = true)]
|
||||
internal static extern uint GetEuid();
|
||||
#else
|
||||
[LibraryImport("libc", EntryPoint = "geteuid", SetLastError = true)]
|
||||
internal static partial uint GetEuid();
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern nint GetStdHandle(EStandardHandle nStdHandle);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial nint GetStdHandle(EStandardHandle nStdHandle);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool SetConsoleMode(nint hConsoleHandle, EConsoleMode dwMode);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial bool SetConsoleMode(nint hConsoleHandle, EConsoleMode dwMode);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern void ShowWindow(nint hWnd, EShowWindow nCmdShow);
|
||||
#else
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial void ShowWindow(nint hWnd, EShowWindow nCmdShow);
|
||||
#endif
|
||||
|
||||
[Flags]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -43,22 +43,17 @@ internal static class OS {
|
||||
internal static readonly string ProcessFileName = Environment.ProcessPath ?? throw new InvalidOperationException(nameof(ProcessFileName));
|
||||
|
||||
internal static DateTime ProcessStartTime {
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
get => RuntimeMadness.ProcessStartTime.ToUniversalTime();
|
||||
#else
|
||||
get {
|
||||
using Process process = Process.GetCurrentProcess();
|
||||
|
||||
return process.StartTime.ToUniversalTime();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static string Version {
|
||||
get {
|
||||
if (!string.IsNullOrEmpty(BackingVersion)) {
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
return BackingVersion!;
|
||||
return BackingVersion;
|
||||
}
|
||||
|
||||
string framework = RuntimeInformation.FrameworkDescription.Trim();
|
||||
@@ -67,15 +62,11 @@ internal static class OS {
|
||||
framework = "Unknown Framework";
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
string runtime = RuntimeInformation.OSArchitecture.ToString();
|
||||
#else
|
||||
string runtime = RuntimeInformation.RuntimeIdentifier.Trim();
|
||||
|
||||
if (runtime.Length == 0) {
|
||||
runtime = "Unknown Runtime";
|
||||
}
|
||||
#endif
|
||||
|
||||
string description = RuntimeInformation.OSDescription.Trim();
|
||||
|
||||
@@ -118,9 +109,7 @@ internal static class OS {
|
||||
}
|
||||
|
||||
internal static string GetOsResourceName(string objectName) {
|
||||
if (string.IsNullOrEmpty(objectName)) {
|
||||
throw new ArgumentNullException(nameof(objectName));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(objectName);
|
||||
|
||||
return $"{SharedInfo.AssemblyName}-{objectName}";
|
||||
}
|
||||
@@ -140,7 +129,7 @@ internal static class OS {
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(optimizationMode));
|
||||
throw new InvalidOperationException(nameof(optimizationMode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,38 +201,6 @@ internal static class OS {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SharedInfo.BuildInfo.Variant.EndsWith("-netf", StringComparison.Ordinal)) {
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
// All Windows variants (7+) have valid .NET Core build
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-Windows variants of generic-netf are supported only in Mono
|
||||
if (!RuntimeMadness.IsRunningOnMono) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Platforms not supported by .NET Core
|
||||
return RuntimeInformation.OSArchitecture switch {
|
||||
// Sadly we can't tell a difference between ARMv6 and ARMv7 reliably, we'll believe that this linux-arm user knows what he's doing and he's indeed in need of generic-netf on ARMv6
|
||||
Architecture.Arm => true,
|
||||
|
||||
// Apart from real x86, this also covers all unknown architectures, such as sparc, ppc64, and anything else Mono might support, we're fine with that
|
||||
Architecture.X86 => true,
|
||||
|
||||
// Everything else is covered by .NET Core
|
||||
_ => false
|
||||
};
|
||||
#else
|
||||
|
||||
// .NET Framework build running on .NET Core? Very funny - only if somebody lied during build process
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(SharedInfo.BuildInfo.Variant), SharedInfo.BuildInfo.Variant));
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (SharedInfo.BuildInfo.Variant == "generic") {
|
||||
// Generic is supported everywhere
|
||||
return true;
|
||||
@@ -298,7 +255,7 @@ internal static class OS {
|
||||
}
|
||||
|
||||
// This function calls unmanaged API in order to tell Windows OS that it should not enter sleep state while the program is running
|
||||
// If user wishes to enter sleep mode, then he should use ShutdownOnFarmingFinished or manage ASF process with third-party tool or script
|
||||
// If user wishes to enter sleep mode, then they should use ShutdownOnFarmingFinished or manage the ASF process with third-party tool or script
|
||||
// See https://docs.microsoft.com/windows/win32/api/winbase/nf-winbase-setthreadexecutionstate for more details
|
||||
NativeMethods.EExecutionState result = NativeMethods.SetThreadExecutionState(NativeMethods.EExecutionState.Awake);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,13 +21,15 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Resources;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
@@ -37,6 +39,7 @@ using ArchiSteamFarm.Storage;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using SteamKit2;
|
||||
using Zxcvbn;
|
||||
|
||||
@@ -46,7 +49,16 @@ public static class Utilities {
|
||||
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"
|
||||
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);
|
||||
|
||||
[PublicAPI]
|
||||
public static string GenerateChecksumFor(byte[] source) {
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
byte[] hash = SHA512.HashData(source);
|
||||
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
|
||||
@@ -56,18 +68,14 @@ public static class Utilities {
|
||||
throw new InvalidOperationException($"{nameof(args.Length)} && {nameof(argsToSkip)}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(delimiter)) {
|
||||
throw new ArgumentNullException(nameof(delimiter));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(delimiter);
|
||||
|
||||
return string.Join(delimiter, args.Skip(argsToSkip));
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static string GetArgsAsText(string text, byte argsToSkip) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
string[] args = text.Split(Array.Empty<char>(), argsToSkip + 1, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@@ -78,18 +86,11 @@ public static class Utilities {
|
||||
public static string? GetCookieValue(this CookieContainer cookieContainer, Uri uri, string name) {
|
||||
ArgumentNullException.ThrowIfNull(cookieContainer);
|
||||
ArgumentNullException.ThrowIfNull(uri);
|
||||
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
|
||||
CookieCollection cookies = cookieContainer.GetCookies(uri);
|
||||
|
||||
#if NETFRAMEWORK || NETSTANDARD
|
||||
return cookies.Count > 0 ? (from Cookie cookie in cookies where cookie.Name == name select cookie.Value).FirstOrDefault() : null;
|
||||
#else
|
||||
return cookies.Count > 0 ? cookies.FirstOrDefault(cookie => cookie.Name == name)?.Value : null;
|
||||
#endif
|
||||
return cookies.FirstOrDefault(cookie => cookie.Name == name)?.Value;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -119,24 +120,18 @@ public static class Utilities {
|
||||
public static async Task<IList<T>> InParallel<T>(IEnumerable<Task<T>> tasks) {
|
||||
ArgumentNullException.ThrowIfNull(tasks);
|
||||
|
||||
IList<T> results;
|
||||
|
||||
switch (ASF.GlobalConfig?.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
results = new List<T>();
|
||||
List<T> results = [];
|
||||
|
||||
foreach (Task<T> task in tasks) {
|
||||
results.Add(await task.ConfigureAwait(false));
|
||||
}
|
||||
|
||||
break;
|
||||
return results;
|
||||
default:
|
||||
results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
return await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -171,18 +166,14 @@ public static class Utilities {
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsValidCdKey(string key) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
|
||||
return GeneratedRegexes.CdKey().IsMatch(key);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsValidHexadecimalText(string text) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
|
||||
}
|
||||
@@ -255,11 +246,26 @@ public static class Utilities {
|
||||
return job.ToTask();
|
||||
}
|
||||
|
||||
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
throw new ArgumentNullException(nameof(directory));
|
||||
[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) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
if (!Directory.Exists(directory)) {
|
||||
return;
|
||||
}
|
||||
@@ -286,9 +292,7 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
internal static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) {
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
throw new ArgumentNullException(nameof(directory));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
#pragma warning disable CA1508 // False positive, params could be null when explicitly set
|
||||
if ((prefixes == null) || (prefixes.Length == 0)) {
|
||||
@@ -300,9 +304,7 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
internal static (bool IsWeak, string? Reason) TestPasswordStrength(string password, ISet<string>? additionallyForbiddenPhrases = null) {
|
||||
if (string.IsNullOrEmpty(password)) {
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
ArgumentException.ThrowIfNullOrEmpty(password);
|
||||
|
||||
HashSet<string> forbiddenPhrases = ForbiddenPasswordPhrases.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
@@ -332,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) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,6 +23,7 @@ using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers;
|
||||
@@ -30,66 +31,73 @@ namespace ArchiSteamFarm.Helpers;
|
||||
public sealed class ArchiCacheable<T> : IDisposable {
|
||||
private readonly TimeSpan CacheLifetime;
|
||||
private readonly SemaphoreSlim InitSemaphore = new(1, 1);
|
||||
private readonly Func<Task<(bool Success, T? Result)>> ResolveFunction;
|
||||
private readonly Func<CancellationToken, Task<(bool Success, T? Result)>> ResolveFunction;
|
||||
|
||||
private bool IsInitialized => InitializedAt > DateTime.MinValue;
|
||||
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 T? InitializedValue;
|
||||
|
||||
public ArchiCacheable(Func<Task<(bool Success, T? Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
|
||||
ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction));
|
||||
public ArchiCacheable(Func<CancellationToken, Task<(bool Success, T? Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
|
||||
ArgumentNullException.ThrowIfNull(resolveFunction);
|
||||
|
||||
ResolveFunction = resolveFunction;
|
||||
CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan;
|
||||
}
|
||||
|
||||
public void Dispose() => InitSemaphore.Dispose();
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<(bool Success, T? Result)> GetValue(ECacheFallback cacheFallback = ECacheFallback.DefaultForType) {
|
||||
public async Task<(bool Success, T? Result)> GetValue(ECacheFallback cacheFallback = ECacheFallback.DefaultForType, CancellationToken cancellationToken = default) {
|
||||
if (!Enum.IsDefined(cacheFallback)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
||||
}
|
||||
|
||||
if (IsInitialized && IsRecent) {
|
||||
if (IsRecent) {
|
||||
return (true, InitializedValue);
|
||||
}
|
||||
|
||||
await InitSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try {
|
||||
await InitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
} catch (OperationCanceledException e) {
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
return ReturnFailedValueFor(cacheFallback);
|
||||
}
|
||||
|
||||
try {
|
||||
if (IsInitialized && IsRecent) {
|
||||
if (IsRecent) {
|
||||
return (true, InitializedValue);
|
||||
}
|
||||
|
||||
(bool success, T? result) = await ResolveFunction().ConfigureAwait(false);
|
||||
(bool success, T? result) = await ResolveFunction(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!success) {
|
||||
return cacheFallback switch {
|
||||
ECacheFallback.DefaultForType => (false, default(T?)),
|
||||
ECacheFallback.FailedNow => (false, result),
|
||||
ECacheFallback.SuccessPreviously => (false, InitializedValue),
|
||||
_ => throw new InvalidOperationException(nameof(cacheFallback))
|
||||
};
|
||||
return ReturnFailedValueFor(cacheFallback, result);
|
||||
}
|
||||
|
||||
InitializedValue = result;
|
||||
InitializedAt = DateTime.UtcNow;
|
||||
|
||||
return (true, result);
|
||||
} catch (OperationCanceledException e) {
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
return ReturnFailedValueFor(cacheFallback);
|
||||
} finally {
|
||||
InitSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task Reset() {
|
||||
public async Task Reset(CancellationToken cancellationToken = default) {
|
||||
if (!IsInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
await InitSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await InitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (!IsInitialized) {
|
||||
@@ -101,4 +109,17 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
||||
InitSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private (bool Success, T? Result) ReturnFailedValueFor(ECacheFallback cacheFallback, T? result = default) {
|
||||
if (!Enum.IsDefined(cacheFallback)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
||||
}
|
||||
|
||||
return cacheFallback switch {
|
||||
ECacheFallback.DefaultForType => (false, default(T?)),
|
||||
ECacheFallback.FailedNow => (false, result),
|
||||
ECacheFallback.SuccessPreviously => (false, InitializedValue),
|
||||
_ => throw new InvalidOperationException(nameof(cacheFallback))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user