mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-31 13:40:46 +00:00
Compare commits
943 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be5ec43772 | ||
|
|
e6ac3f7daf | ||
|
|
a41ef5dd65 | ||
|
|
14efac34aa | ||
|
|
643b8a60fc | ||
|
|
047a3ca1d9 | ||
|
|
e3d15dd71b | ||
|
|
43057302da | ||
|
|
8b6c6dea15 | ||
|
|
1c99f2476d | ||
|
|
7894b0132f | ||
|
|
5c53f65bc1 | ||
|
|
55c0b08d93 | ||
|
|
fc20b6cfc4 | ||
|
|
c9cae6e258 | ||
|
|
4e382732d9 | ||
|
|
98ef37e722 | ||
|
|
fd517294d1 | ||
|
|
3be6bf8aca | ||
|
|
0f4a0d24f0 | ||
|
|
1c15b5940d | ||
|
|
7b4e9209d5 | ||
|
|
627174eb6b | ||
|
|
1bdf0b3f29 | ||
|
|
13a55a1df8 | ||
|
|
1da32509d7 | ||
|
|
e0b1bbc6e0 | ||
|
|
2b6996e77b | ||
|
|
cf8773c610 | ||
|
|
150eb2d624 | ||
|
|
a53a194e6b | ||
|
|
0cfdbf6b1a | ||
|
|
59130bd244 | ||
|
|
4b86438686 | ||
|
|
9f6b9c211b | ||
|
|
f468e641ba | ||
|
|
05aadd45c9 | ||
|
|
51b9708f8d | ||
|
|
c7040cc9bd | ||
|
|
cde0e2b654 | ||
|
|
7a628873a9 | ||
|
|
ec539ea483 | ||
|
|
512a479c95 | ||
|
|
a65a8aeb47 | ||
|
|
4101d44b9c | ||
|
|
5309547882 | ||
|
|
761c278b76 | ||
|
|
e994dca817 | ||
|
|
9ea2d56f4b | ||
|
|
fe2eee859f | ||
|
|
e5126cf7d4 | ||
|
|
d67e5ebc0a | ||
|
|
f38d6a8ab0 | ||
|
|
113e0c9b3c | ||
|
|
32a2c1b232 | ||
|
|
c84e265133 | ||
|
|
462e7785c7 | ||
|
|
0bc0060b3a | ||
|
|
d87c5a23cf | ||
|
|
bbd2892875 | ||
|
|
e6171456c3 | ||
|
|
4816cd006d | ||
|
|
239c536815 | ||
|
|
6e138421f6 | ||
|
|
8e13a066b8 | ||
|
|
7df7d3400b | ||
|
|
f2d492ebd4 | ||
|
|
7228ad2bfc | ||
|
|
0d0ebf80bc | ||
|
|
afe9842f76 | ||
|
|
caa54a7f2b | ||
|
|
13480a44ff | ||
|
|
d794ffd58c | ||
|
|
9350e646d5 | ||
|
|
f8a1a84df1 | ||
|
|
d3c5236fea | ||
|
|
f0190ad541 | ||
|
|
0a58483901 | ||
|
|
6a82eac54c | ||
|
|
6f976dd7e9 | ||
|
|
3dfecef79b | ||
|
|
a5303ced19 | ||
|
|
5580546022 | ||
|
|
55771915b9 | ||
|
|
93b67b8f95 | ||
|
|
9d795e7337 | ||
|
|
513c21c58e | ||
|
|
90e5ea38ed | ||
|
|
f0473c4119 | ||
|
|
345f94d7e7 | ||
|
|
70ec5cb6d4 | ||
|
|
3b7e4479a1 | ||
|
|
8d2b07e671 | ||
|
|
22b7494586 | ||
|
|
29a3168d49 | ||
|
|
14421b606e | ||
|
|
3cf4ef3466 | ||
|
|
cee7cd03b2 | ||
|
|
f395604193 | ||
|
|
58b1f0f6c4 | ||
|
|
82282230e7 | ||
|
|
9ffa74ae7a | ||
|
|
ea32ec55fc | ||
|
|
42abea414b | ||
|
|
f2c8f2d349 | ||
|
|
9ba8e021d2 | ||
|
|
f3255665ad | ||
|
|
0a5a447b6d | ||
|
|
8bf2504acf | ||
|
|
27e63c3849 | ||
|
|
d2ca725016 | ||
|
|
cd32c90c68 | ||
|
|
3bf0c76c7f | ||
|
|
9ced15cd5a | ||
|
|
19de1644bd | ||
|
|
24195022b6 | ||
|
|
3edebc9f0e | ||
|
|
5dc5cc6524 | ||
|
|
45e8f55971 | ||
|
|
d37707b523 | ||
|
|
f55f58a8ef | ||
|
|
d524fc939f | ||
|
|
100e2d58ad | ||
|
|
9a10aeb4f1 | ||
|
|
bd8fda2d9b | ||
|
|
dca76c5bbe | ||
|
|
792d1807a7 | ||
|
|
cfcbfabe6c | ||
|
|
72a6955ee1 | ||
|
|
163aab742c | ||
|
|
4eb3f07426 | ||
|
|
d0ba18a031 | ||
|
|
4805c82238 | ||
|
|
3410f84279 | ||
|
|
e4f3cc0bd5 | ||
|
|
38d3e1e0cb | ||
|
|
6a84848a22 | ||
|
|
0feb9196e6 | ||
|
|
510193cfc6 | ||
|
|
c6f830fa27 | ||
|
|
fa7f39d2a7 | ||
|
|
cae8180e6c | ||
|
|
f3eb46ea75 | ||
|
|
631e4a1730 | ||
|
|
083fefe3d6 | ||
|
|
ef2d63d918 | ||
|
|
bce0649822 | ||
|
|
17563149b6 | ||
|
|
0af94d491f | ||
|
|
3d674ab781 | ||
|
|
c7b39ddad9 | ||
|
|
8b870b290f | ||
|
|
91c28c60de | ||
|
|
ea7b944114 | ||
|
|
2420f39718 | ||
|
|
bb1f55d809 | ||
|
|
49858fa18e | ||
|
|
4be3a59704 | ||
|
|
0d8cb84b06 | ||
|
|
0b36b92d62 | ||
|
|
98938db3d2 | ||
|
|
cf3e319489 | ||
|
|
a7619f7ff5 | ||
|
|
5bb32c4e94 | ||
|
|
1421152e27 | ||
|
|
354b58970e | ||
|
|
9d30f68a85 | ||
|
|
bf5188530d | ||
|
|
98b121a7bd | ||
|
|
557f0304a2 | ||
|
|
b0632a6c8d | ||
|
|
ce37888609 | ||
|
|
7aa862731f | ||
|
|
1abfd37d71 | ||
|
|
77e12476e0 | ||
|
|
4b170a5c12 | ||
|
|
07f659d22c | ||
|
|
c72d7009dc | ||
|
|
9f33fa5e72 | ||
|
|
9a55ab7518 | ||
|
|
5985d07719 | ||
|
|
fac5204ad2 | ||
|
|
1a298143b7 | ||
|
|
00d3f9ced3 | ||
|
|
d810c64120 | ||
|
|
47d89415b4 | ||
|
|
96296028c1 | ||
|
|
6644ec18de | ||
|
|
2b7fed251c | ||
|
|
9cffd3a133 | ||
|
|
7581d58aa5 | ||
|
|
24b8b2f9b8 | ||
|
|
f3bb15670b | ||
|
|
5dbe523f59 | ||
|
|
319f1218de | ||
|
|
ee9d145e6b | ||
|
|
0a70e069e0 | ||
|
|
8fdd8e91a6 | ||
|
|
533bf45f35 | ||
|
|
6227f84931 | ||
|
|
e6579e4355 | ||
|
|
321e02c0ff | ||
|
|
406a5f1fd1 | ||
|
|
40822dff89 | ||
|
|
f72345aaa2 | ||
|
|
32cdb45e9f | ||
|
|
72cabc51d8 | ||
|
|
e62f850a3a | ||
|
|
8b62c6c4d2 | ||
|
|
f7534cf7ec | ||
|
|
8797a7ed78 | ||
|
|
a1d729992c | ||
|
|
5a182b7522 | ||
|
|
d73c04a2db | ||
|
|
b57cc367d7 | ||
|
|
5fe4053f8f | ||
|
|
89ff5b7f4d | ||
|
|
ed7ae8054a | ||
|
|
8bc2fa4334 | ||
|
|
5bf8a364d4 | ||
|
|
0597bda6b9 | ||
|
|
4d084a3573 | ||
|
|
9322626986 | ||
|
|
5317f1239b | ||
|
|
012ed1b9b9 | ||
|
|
4ba3e0c536 | ||
|
|
c66b52ee1d | ||
|
|
195fb030bb | ||
|
|
a5dd87932f | ||
|
|
31295babac | ||
|
|
6ee2696c53 | ||
|
|
f09c7cbb19 | ||
|
|
6557d13ceb | ||
|
|
34b8f2733c | ||
|
|
5398e0a282 | ||
|
|
e7887c8bdf | ||
|
|
ee4d5561dd | ||
|
|
2838128bd7 | ||
|
|
40701eef72 | ||
|
|
f31bc799ca | ||
|
|
f383f8333e | ||
|
|
f9bd7683a6 | ||
|
|
d6ef39b5e4 | ||
|
|
58f437ca83 | ||
|
|
c3c9a41ae1 | ||
|
|
a810519f76 | ||
|
|
1a46ff430a | ||
|
|
a15c0dbe17 | ||
|
|
9c34383ec4 | ||
|
|
8bed61050d | ||
|
|
dfed0ddd51 | ||
|
|
b67629b05f | ||
|
|
50c6209c41 | ||
|
|
d787d73e68 | ||
|
|
6bde5527e1 | ||
|
|
62dd27bfd8 | ||
|
|
5bacb0b194 | ||
|
|
ba0e7e258e | ||
|
|
13ab06d455 | ||
|
|
8bbfb1bf3b | ||
|
|
665a832cbd | ||
|
|
3c5527a8d2 | ||
|
|
6ca91d7045 | ||
|
|
d775874e48 | ||
|
|
fed3e67b3b | ||
|
|
4ddb072e9e | ||
|
|
bf4b03f562 | ||
|
|
36ca148bad | ||
|
|
c50dbe6f68 | ||
|
|
3bfc831966 | ||
|
|
12de692f2a | ||
|
|
52437a62d3 | ||
|
|
5b11623fad | ||
|
|
125c8fd58e | ||
|
|
1b476cd900 | ||
|
|
22107c1f11 | ||
|
|
5b527e44e7 | ||
|
|
c7283ea815 | ||
|
|
722ed3e5a5 | ||
|
|
712b53cb72 | ||
|
|
57956e0ed9 | ||
|
|
dc0502a671 | ||
|
|
3df60fc16d | ||
|
|
813d2e3395 | ||
|
|
6ab4e2cdee | ||
|
|
cc55e386c9 | ||
|
|
582f508f33 | ||
|
|
63c188cd03 | ||
|
|
9fd4ef2b63 | ||
|
|
9b3f776c92 | ||
|
|
448c3c8e51 | ||
|
|
af241e29fb | ||
|
|
734f2064fc | ||
|
|
f0989465d9 | ||
|
|
f0c8368c7b | ||
|
|
672a7da514 | ||
|
|
2755967658 | ||
|
|
53f7abdaae | ||
|
|
d4efa7841a | ||
|
|
520063d5b1 | ||
|
|
bb859a45b8 | ||
|
|
b8ec1890b1 | ||
|
|
a103f1406f | ||
|
|
a6c54f4503 | ||
|
|
0ec44418f7 | ||
|
|
9915dbdd2c | ||
|
|
f5b9fc3e87 | ||
|
|
a143319405 | ||
|
|
42d5d3ed12 | ||
|
|
f5e9f70280 | ||
|
|
39832a3114 | ||
|
|
82ad062f08 | ||
|
|
3602f49a1e | ||
|
|
58a5c9aa42 | ||
|
|
0b83f8ece0 | ||
|
|
1e03077dc7 | ||
|
|
019893ee43 | ||
|
|
f5d165493d | ||
|
|
3204d6ec16 | ||
|
|
6f0eacf615 | ||
|
|
3584bdf494 | ||
|
|
1157af92b1 | ||
|
|
79f7b63b55 | ||
|
|
ed84d98ded | ||
|
|
9ce770691e | ||
|
|
3f9eba35dc | ||
|
|
75ab255a98 | ||
|
|
bc6a96edc6 | ||
|
|
28d46acc4f | ||
|
|
9912c851dc | ||
|
|
e27c272dd6 | ||
|
|
1b3ce37480 | ||
|
|
c4e843fc6d | ||
|
|
bfbe96ef23 | ||
|
|
ab61915c02 | ||
|
|
937fe8d07e | ||
|
|
cbbf743eb2 | ||
|
|
f8f3da1a40 | ||
|
|
b7d215be89 | ||
|
|
0d96f7907e | ||
|
|
a8c4c3910a | ||
|
|
5a893bddeb | ||
|
|
da4e867007 | ||
|
|
558fa75dd7 | ||
|
|
bdf5bc25d4 | ||
|
|
8d783f11ae | ||
|
|
adf83e5284 | ||
|
|
bdb8f90a4e | ||
|
|
d67366cd12 | ||
|
|
065d44738b | ||
|
|
2af5e0ca02 | ||
|
|
074127edbb | ||
|
|
0ceef1b7e2 | ||
|
|
fd84b16fa0 | ||
|
|
e9c02a4140 | ||
|
|
270a71e83c | ||
|
|
7c1dde5c72 | ||
|
|
f3229fa45f | ||
|
|
5c6ca3fee2 | ||
|
|
179affd49c | ||
|
|
79a4638eea | ||
|
|
feede84577 | ||
|
|
f3f71cfb27 | ||
|
|
a785ae3536 | ||
|
|
8663bd1eb4 | ||
|
|
b869df538a | ||
|
|
e8e7d0f1cb | ||
|
|
bea85a3014 | ||
|
|
07972e3714 | ||
|
|
13141f35a7 | ||
|
|
47ace2e526 | ||
|
|
f44556a863 | ||
|
|
fa34c853b7 | ||
|
|
6b6c60de92 | ||
|
|
4d5152a6ae | ||
|
|
d74ae770fe | ||
|
|
b8d7f24d50 | ||
|
|
5063bda7ae | ||
|
|
946308366a | ||
|
|
7e84d26d5c | ||
|
|
7cae68f14a | ||
|
|
f0a2c26a1a | ||
|
|
d146525d9c | ||
|
|
0b2cdb63b2 | ||
|
|
c7a1713066 | ||
|
|
cf23819b48 | ||
|
|
78d5234047 | ||
|
|
a0d7ef5856 | ||
|
|
1dc2b1e06e | ||
|
|
70fe873eaf | ||
|
|
56754053c3 | ||
|
|
cb883cf235 | ||
|
|
661786adf2 | ||
|
|
6c63b6db68 | ||
|
|
dfdb0a22a0 | ||
|
|
41ecfb1d02 | ||
|
|
25b29cd56e | ||
|
|
ab5fb6dd1b | ||
|
|
75e4557da3 | ||
|
|
95c3658197 | ||
|
|
26e7f7deb5 | ||
|
|
f003dcda0b | ||
|
|
296318060f | ||
|
|
f80b114892 | ||
|
|
604652c03d | ||
|
|
2420d34b22 | ||
|
|
21a5793c45 | ||
|
|
888b45c919 | ||
|
|
35bf243f1a | ||
|
|
f4c1dededc | ||
|
|
d5f355a2bc | ||
|
|
eb9b5dd025 | ||
|
|
2cec35f911 | ||
|
|
7d4438b089 | ||
|
|
2782329549 | ||
|
|
14ac124e0c | ||
|
|
ec07d23cc4 | ||
|
|
3299ba8c10 | ||
|
|
4eb09f950d | ||
|
|
144a1d1574 | ||
|
|
05f3aada38 | ||
|
|
9240500e2c | ||
|
|
98768970a7 | ||
|
|
7e41e530e7 | ||
|
|
97198fd435 | ||
|
|
e8b83b8ad4 | ||
|
|
929a1dfd82 | ||
|
|
ea7bad2868 | ||
|
|
71f54a79f3 | ||
|
|
0cd7b10c9a | ||
|
|
42fb71f856 | ||
|
|
7b3ae25d58 | ||
|
|
2961975f05 | ||
|
|
06843ebf9f | ||
|
|
6ca395795c | ||
|
|
0e5490cc3a | ||
|
|
39cc6e6ea6 | ||
|
|
70544d1d76 | ||
|
|
b1e9a53adc | ||
|
|
72e59e7271 | ||
|
|
1ae3517374 | ||
|
|
975b3b89f3 | ||
|
|
a982520844 | ||
|
|
0ed4c7536a | ||
|
|
6e360b7e1a | ||
|
|
17c79904e4 | ||
|
|
0fcbc8c402 | ||
|
|
9e0d44bee2 | ||
|
|
a093f24a9d | ||
|
|
e6a51bae55 | ||
|
|
a992d0c3cd | ||
|
|
a0e5ae8f46 | ||
|
|
c4a46fbdde | ||
|
|
d899dbc18c | ||
|
|
04e14293ef | ||
|
|
18a1b0a883 | ||
|
|
5ca028ef47 | ||
|
|
43ec8f9566 | ||
|
|
32f5b3a1c5 | ||
|
|
196afbf276 | ||
|
|
1f0e4c9058 | ||
|
|
0ded9698b2 | ||
|
|
9825f007c0 | ||
|
|
bc38ba478d | ||
|
|
2f22757fea | ||
|
|
3ff0468926 | ||
|
|
a6a973468c | ||
|
|
4aa1604dfb | ||
|
|
44c7fcd131 | ||
|
|
ce610ab24d | ||
|
|
c76f17c5c7 | ||
|
|
30b4e006dc | ||
|
|
39621ed46e | ||
|
|
e532b57369 | ||
|
|
b117c5164d | ||
|
|
be5a6bc27a | ||
|
|
0ecb04e62c | ||
|
|
0af9f99923 | ||
|
|
cd0078e83e | ||
|
|
10cedad0ee | ||
|
|
693f4edbe5 | ||
|
|
ed44ad030e | ||
|
|
d338477e5c | ||
|
|
053cb5fc03 | ||
|
|
a01ac6641e | ||
|
|
3deb560e5e | ||
|
|
1861add350 | ||
|
|
83fac5b115 | ||
|
|
23e49dafbc | ||
|
|
b71462b151 | ||
|
|
88d3b19196 | ||
|
|
237f23e965 | ||
|
|
776755d3ab | ||
|
|
e1e464b4e7 | ||
|
|
d590a30f20 | ||
|
|
1d520d9071 | ||
|
|
772607b680 | ||
|
|
82750352e2 | ||
|
|
3e5a6a7b32 | ||
|
|
5f803cf725 | ||
|
|
ffdc0e89e8 | ||
|
|
b608083bfc | ||
|
|
ebdb17d71b | ||
|
|
8877468bd4 | ||
|
|
c753ed24cd | ||
|
|
dcebde55a2 | ||
|
|
bd0f1779d6 | ||
|
|
164d9330f0 | ||
|
|
7c1c0d61b4 | ||
|
|
2aab79ec52 | ||
|
|
917df358e8 | ||
|
|
5b62e19a80 | ||
|
|
cb99c916dd | ||
|
|
2596c74d2e | ||
|
|
8147dacae7 | ||
|
|
3ad324ebea | ||
|
|
6e34c14aef | ||
|
|
96c9ab34d6 | ||
|
|
fa6649305e | ||
|
|
c0a3b67ebf | ||
|
|
2a845ab46f | ||
|
|
5ca6e41691 | ||
|
|
6eb9b9b26d | ||
|
|
95a6cef6db | ||
|
|
688e1cea83 | ||
|
|
40a479b1df | ||
|
|
b535886959 | ||
|
|
45d0a8a9c1 | ||
|
|
3ee2ded814 | ||
|
|
a2b5f80f40 | ||
|
|
0fffbdaa52 | ||
|
|
2ec764f8ec | ||
|
|
c67aecacbc | ||
|
|
ae5b9cdc0d | ||
|
|
0eab63bab5 | ||
|
|
1aea4c0550 | ||
|
|
16c4bed95f | ||
|
|
8948817d55 | ||
|
|
6ff943aeaa | ||
|
|
0576bbd3aa | ||
|
|
6e70956ee9 | ||
|
|
77409699f0 | ||
|
|
dae3e93031 | ||
|
|
3041850b92 | ||
|
|
7c00d8d03d | ||
|
|
aedad9d5b3 | ||
|
|
ff7f661197 | ||
|
|
e57cc21b89 | ||
|
|
06bfe01087 | ||
|
|
bcceb0c39c | ||
|
|
996ee66554 | ||
|
|
dad19956aa | ||
|
|
beeda2777d | ||
|
|
53e06a7392 | ||
|
|
9a1d4913a0 | ||
|
|
c65b40b45b | ||
|
|
23647f2e39 | ||
|
|
feb7a72bd1 | ||
|
|
7fe5989f5d | ||
|
|
715ed034df | ||
|
|
d82df0074f | ||
|
|
03c2ba049e | ||
|
|
03bce5dd71 | ||
|
|
023e38d5e0 | ||
|
|
6178b12bb1 | ||
|
|
df95b82b10 | ||
|
|
93ac0b4e4a | ||
|
|
38fc3ba6a3 | ||
|
|
2e87b78b45 | ||
|
|
dae256f069 | ||
|
|
8452c46c47 | ||
|
|
68e30b43c2 | ||
|
|
c9b1e46013 | ||
|
|
fd9770d78e | ||
|
|
a4374389b8 | ||
|
|
082cab42df | ||
|
|
3ff80b37f3 | ||
|
|
07c354f9e7 | ||
|
|
b83f8fc669 | ||
|
|
d6a2f53ab0 | ||
|
|
0261623ea9 | ||
|
|
b5ca484c2b | ||
|
|
a7c30e4878 | ||
|
|
f26a4ae864 | ||
|
|
c2018b53a5 | ||
|
|
55421bb29f | ||
|
|
52eabe4daf | ||
|
|
86fc8a765c | ||
|
|
9162752b99 | ||
|
|
4436e8dc43 | ||
|
|
1f0b996cf5 | ||
|
|
4dc7acb914 | ||
|
|
d570a17532 | ||
|
|
e5184adede | ||
|
|
13a5fa7c02 | ||
|
|
c698fe7b07 | ||
|
|
a08c85e40b | ||
|
|
055af32219 | ||
|
|
080b500ebf | ||
|
|
4a329b0b15 | ||
|
|
e2494960ae | ||
|
|
2e987ccee6 | ||
|
|
99284e22c9 | ||
|
|
37eac5844e | ||
|
|
35f295a860 | ||
|
|
29d047271e | ||
|
|
948a86bfc9 | ||
|
|
f853c61821 | ||
|
|
5b1cb16c98 | ||
|
|
bdac1b2782 | ||
|
|
7532b89fd0 | ||
|
|
7cd351d1cd | ||
|
|
9c8d63318e | ||
|
|
f0c0e07489 | ||
|
|
d2e79ff3a4 | ||
|
|
ab7b998e3b | ||
|
|
9c88d14c8e | ||
|
|
9a3c3bdbaf | ||
|
|
7b3598af20 | ||
|
|
35d2156855 | ||
|
|
d589da7a39 | ||
|
|
c10de94bd0 | ||
|
|
d164296d7e | ||
|
|
b378a76072 | ||
|
|
263a2db476 | ||
|
|
61549fc983 | ||
|
|
19aad04143 | ||
|
|
5a5f3c6786 | ||
|
|
bd68df2fd6 | ||
|
|
2a97644468 | ||
|
|
5304ca3e07 | ||
|
|
f0471ac0eb | ||
|
|
b0e7f1963c | ||
|
|
78990e8aff | ||
|
|
b871970d85 | ||
|
|
d563a20288 | ||
|
|
b2fefa4476 | ||
|
|
840cf25ea4 | ||
|
|
79587f68d7 | ||
|
|
45adf9c1a1 | ||
|
|
1dcf98c849 | ||
|
|
a826b7f9b7 | ||
|
|
85c8397cf7 | ||
|
|
dbf1c1ba51 | ||
|
|
359439e306 | ||
|
|
763766e092 | ||
|
|
a4a347e957 | ||
|
|
844ca93647 | ||
|
|
16fe445ea9 | ||
|
|
34bf8fb84f | ||
|
|
4873cd337a | ||
|
|
8c0249a62d | ||
|
|
220ecf0c38 | ||
|
|
ebd79425f4 | ||
|
|
2eaf934dde | ||
|
|
599cd9bff8 | ||
|
|
339e83a818 | ||
|
|
f083bb2d3b | ||
|
|
9f68d17a28 | ||
|
|
d8413f9633 | ||
|
|
27d9d61309 | ||
|
|
2fc92ce427 | ||
|
|
86d94a7bbe | ||
|
|
9647db8bf7 | ||
|
|
2ce5018d62 | ||
|
|
61768dbeb9 | ||
|
|
37ced5d4e3 | ||
|
|
c1a695de7b | ||
|
|
7040bdabf6 | ||
|
|
14512aec71 | ||
|
|
40d67ac185 | ||
|
|
7de67e84f9 | ||
|
|
6c9df75a1a | ||
|
|
b98d8405e1 | ||
|
|
062b241232 | ||
|
|
b5af510eb9 | ||
|
|
2edcb7a0ce | ||
|
|
58cf93ff48 | ||
|
|
9f0f3339c5 | ||
|
|
6b88d49067 | ||
|
|
b4e102682f | ||
|
|
a580c85234 | ||
|
|
a3cce515ec | ||
|
|
1f21c1f9f6 | ||
|
|
9c7014d5c1 | ||
|
|
0a01dfa22b | ||
|
|
a8bb107e23 | ||
|
|
151f6cfe4a | ||
|
|
d21e398ac0 | ||
|
|
16f7f82dc0 | ||
|
|
8a6a02e034 | ||
|
|
b8bfcd5df3 | ||
|
|
2326196e01 | ||
|
|
380d785388 | ||
|
|
edd82b365c | ||
|
|
d49d106d64 | ||
|
|
78407fbd9c | ||
|
|
1a87149765 | ||
|
|
f260015098 | ||
|
|
5016abe45e | ||
|
|
2238897f37 | ||
|
|
493f40a97c | ||
|
|
5ec7ca050b | ||
|
|
f727403295 | ||
|
|
027d23d894 | ||
|
|
f36798b2c3 | ||
|
|
202a92f66f | ||
|
|
48b2a4c859 | ||
|
|
cfbc3d749f | ||
|
|
a76af71227 | ||
|
|
cc5e5dfcc9 | ||
|
|
a185f2f03d | ||
|
|
3772b303c5 | ||
|
|
d6ed6e81a4 | ||
|
|
0bbc85527a | ||
|
|
1eabe3a5ed | ||
|
|
f95b6bf089 | ||
|
|
ff7b4582c7 | ||
|
|
7e7fb9cd16 | ||
|
|
65bbaf628e | ||
|
|
db2cbde708 | ||
|
|
b701acf72f | ||
|
|
ceb021dbdf | ||
|
|
ce1c77780d | ||
|
|
5b7858c2a0 | ||
|
|
635afa7165 | ||
|
|
c79c314b20 | ||
|
|
ec78ad1ac2 | ||
|
|
9273d73640 | ||
|
|
d598b99a1e | ||
|
|
f12b00bb4c | ||
|
|
ff7116d2ac | ||
|
|
ee435fc628 | ||
|
|
c8f02779b6 | ||
|
|
83667ed7c3 | ||
|
|
387ef3e0dd | ||
|
|
7e8c6b7fb3 | ||
|
|
e97b440225 | ||
|
|
b1db99f328 | ||
|
|
ace0ca4555 | ||
|
|
33767ace78 | ||
|
|
5d5a76de40 | ||
|
|
582680f69d | ||
|
|
e65729ee1a | ||
|
|
83a353dfe0 | ||
|
|
aea7c7640c | ||
|
|
7118185ac5 | ||
|
|
b681b74ee6 | ||
|
|
cc83222c3e | ||
|
|
6c570738ee | ||
|
|
393ddf6f10 | ||
|
|
75a7df2751 | ||
|
|
0c3dfaa4ae | ||
|
|
94e70e5ac2 | ||
|
|
9ce527c938 | ||
|
|
660b05e4c4 | ||
|
|
b39efb2b03 | ||
|
|
53e0b62ced | ||
|
|
d3980962fe | ||
|
|
517787efb8 | ||
|
|
d06afa26d4 | ||
|
|
4562e71e47 | ||
|
|
894471fa82 | ||
|
|
e9f6c15ba1 | ||
|
|
814b93d1cf | ||
|
|
beafbd8f43 | ||
|
|
dbd0e006ed | ||
|
|
4661803836 | ||
|
|
99ecd72660 | ||
|
|
799ec2965f | ||
|
|
c7e9c0c3b0 | ||
|
|
1f3e861612 | ||
|
|
159b0620a7 | ||
|
|
e508602be7 | ||
|
|
6edf62d849 | ||
|
|
021d414143 | ||
|
|
813587508e | ||
|
|
0e3d124663 | ||
|
|
91115b7cb7 | ||
|
|
0f12174564 | ||
|
|
d087aacbfb | ||
|
|
1c0d2d88ed | ||
|
|
6b170c345d | ||
|
|
bc8a4a50d2 | ||
|
|
e025df3d9b | ||
|
|
5a97835531 | ||
|
|
bce0557873 | ||
|
|
4c7cd204ce | ||
|
|
7ba6b230df | ||
|
|
6c4fba5173 | ||
|
|
5cd6477b69 | ||
|
|
1f5fbb5f92 | ||
|
|
d0521ff9ca | ||
|
|
1be15716fc | ||
|
|
e00ee2cc55 | ||
|
|
8893fc8e70 | ||
|
|
86b41f0542 | ||
|
|
a245c091a4 | ||
|
|
abbe0cca22 | ||
|
|
d1c2b103b6 | ||
|
|
9f1734efb7 | ||
|
|
729c2e889c | ||
|
|
a9edc7ad7a | ||
|
|
08a6486c00 | ||
|
|
ca3bc1becd | ||
|
|
fe5028a399 | ||
|
|
c1d9d04071 | ||
|
|
e5ae2abbf0 | ||
|
|
41fa5de5a8 | ||
|
|
697b78aa21 | ||
|
|
1a7be0bac8 | ||
|
|
9d88972ae0 | ||
|
|
aec4130afe | ||
|
|
64228cd3d9 | ||
|
|
3568a0e528 | ||
|
|
38c2b51f2b | ||
|
|
450f365817 | ||
|
|
cf3f6aabdf | ||
|
|
842fb6e304 | ||
|
|
a1169331aa | ||
|
|
2fb7d62e06 | ||
|
|
4e57153e91 | ||
|
|
d2e78b6970 | ||
|
|
ccef6554fe | ||
|
|
97875a87c2 | ||
|
|
2684f99563 | ||
|
|
a50318dc8b | ||
|
|
eeccc36fe4 | ||
|
|
1ead134578 | ||
|
|
f03f8ebe70 | ||
|
|
aa8b360e1d | ||
|
|
8c22f9929c | ||
|
|
3795b2de3a | ||
|
|
f4650fe570 | ||
|
|
fec57e0fff | ||
|
|
8e47a5906f | ||
|
|
f728ddf737 | ||
|
|
173cec5ef7 | ||
|
|
d16c4822eb | ||
|
|
03e3d74e51 | ||
|
|
0a3d011e2e | ||
|
|
f112a05569 | ||
|
|
1c579d96ee | ||
|
|
19a0be1d26 | ||
|
|
a8de495c7c | ||
|
|
ab8ceab055 | ||
|
|
64b72d1e55 | ||
|
|
f807bdb660 | ||
|
|
5b66b70566 | ||
|
|
41c06851a5 | ||
|
|
4dbb964ba9 | ||
|
|
11471c759d | ||
|
|
2aa4ab7fe8 | ||
|
|
dfc055c066 | ||
|
|
1a0ac11f46 | ||
|
|
7266864b3b | ||
|
|
b52f746138 | ||
|
|
a2585ec8c9 | ||
|
|
37781698e0 | ||
|
|
2a8fe7611b | ||
|
|
8fdf14bb10 | ||
|
|
31db72b2d6 | ||
|
|
f28ae15cc9 | ||
|
|
6fcc64dad1 | ||
|
|
e18046084e | ||
|
|
c3c5f33289 | ||
|
|
e03734ef8f | ||
|
|
a7c2ca6bc5 | ||
|
|
171fca42f2 | ||
|
|
e90ac74b16 | ||
|
|
a5ce8bf3d7 | ||
|
|
aad77569a7 | ||
|
|
e74b3e4f78 | ||
|
|
7db44c5835 | ||
|
|
25a88f941d | ||
|
|
2eab00facc | ||
|
|
98e51a4543 | ||
|
|
2ee49db81d | ||
|
|
aab397dd2d | ||
|
|
7426fafcb0 | ||
|
|
270bd7ae26 | ||
|
|
4c3713c19f | ||
|
|
5791b1e552 | ||
|
|
5c59236a09 | ||
|
|
a7119bba89 | ||
|
|
3b64e14489 | ||
|
|
5f36ca91d7 | ||
|
|
5a2cd25fa1 | ||
|
|
20a5d509a7 | ||
|
|
0c457e7f3e | ||
|
|
4e6014d652 | ||
|
|
1436fb6d6a | ||
|
|
e2578c7960 | ||
|
|
8fb1a2e1ea | ||
|
|
3e2951d1d0 | ||
|
|
1dcb103bf7 | ||
|
|
7ca8efb81f | ||
|
|
c08f259806 | ||
|
|
e0a8f96ec4 | ||
|
|
dae6f9d328 | ||
|
|
4258fed873 | ||
|
|
ab6e0a1e1b | ||
|
|
959056523a | ||
|
|
245e3aa250 | ||
|
|
170bd9fe42 | ||
|
|
2cf84d3691 | ||
|
|
ae0ec5feee | ||
|
|
c495ad4f4a | ||
|
|
01e4085a52 | ||
|
|
e89dad5792 | ||
|
|
8c6c7a5f3c | ||
|
|
32f52e9de3 | ||
|
|
aaabd81778 | ||
|
|
24200e3490 | ||
|
|
a896075e88 | ||
|
|
1bf35d1215 | ||
|
|
641aa435be | ||
|
|
d3e48e69d4 | ||
|
|
8548044038 | ||
|
|
cdffde2d76 | ||
|
|
afd7360676 | ||
|
|
7603efb289 | ||
|
|
3ad6f68bb9 | ||
|
|
065facb5db | ||
|
|
8140784903 | ||
|
|
c468f3e4e1 | ||
|
|
174317c674 | ||
|
|
25690056da | ||
|
|
1950c1326e | ||
|
|
876074a0ed | ||
|
|
8c06051f52 | ||
|
|
b7d9c7b6da | ||
|
|
ca048912cd | ||
|
|
290aa3ba34 | ||
|
|
8620a90787 | ||
|
|
189f998faf | ||
|
|
a5640f5a84 | ||
|
|
b343d81f56 | ||
|
|
edf2a19946 | ||
|
|
7e43a05517 | ||
|
|
db8ead92a1 | ||
|
|
e33c340183 | ||
|
|
a04781747e |
@@ -22,7 +22,6 @@ ArchiSteamFarm/logs
|
||||
# /_/ \_\____/|_| |____/ \___/ \___|_|\_\___|_|
|
||||
|
||||
# Additional folders that aren't used during image building:
|
||||
|
||||
**/.git*
|
||||
**/[Bb]in/
|
||||
**/[Oo]bj/
|
||||
@@ -31,6 +30,10 @@ ArchiSteamFarm.CustomPlugins.*
|
||||
ASF-ui/dist
|
||||
wiki
|
||||
|
||||
# Add exception for .git used in ASF-ui, it's used for calculating commit hash during build
|
||||
!.git/modules/ASF-ui
|
||||
!ASF-ui/.git
|
||||
|
||||
# _ _
|
||||
# | | (_) _ __ _ _ __ __
|
||||
# | | | || '_ \ | | | |\ \/ /
|
||||
|
||||
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
@@ -67,7 +67,7 @@ ASF is open-source project, developed mainly by **[JustArchi](https://github.com
|
||||
|
||||
### License
|
||||
|
||||
ASF is using **[Apache License 2.0](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/LICENSE-2.0.txt)**.
|
||||
ASF is using **[Apache License 2.0](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/LICENSE.txt)**.
|
||||
|
||||
> Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions.
|
||||
|
||||
@@ -80,7 +80,6 @@ For more info about the license, please check out **[license](https://github.com
|
||||
Please stick with ASF code style when submitting PRs. In repo you can find several different files dedicated to making it easier for you:
|
||||
|
||||
- **[EditorConfig](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.editorconfig)** file which is supported by all major IDEs and requires no further setup. It's a good starting point, although it doesn't include all the rules that we'd like to see.
|
||||
- **[VS settings](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/CodeStyle.vssettings)** file that you can use in Visual Studio for import. This one includes far more options than EditorConfig alone, and it's a very good choice if you're using bare VS.
|
||||
- **[DotSettings](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/ArchiSteamFarm.sln.DotSettings)** file that is being used by JetBrains products, namely **[ReSharper](https://www.jetbrains.com/resharper)** and **[Rider](https://www.jetbrains.com/rider)**. This one is the most complete config file that is also being loaded automatically when you're using ReSharper/Rider with our code.
|
||||
|
||||
Personally we're using **[JetBrains Rider](https://www.jetbrains.com/rider)**, so no other action is needed after opening `ArchiSteamFarm.sln` solution. If you're using VS alone, it's probably a good idea to import our code style settings, although even editor config should be enough for majority of cases. If you can save us those few extra seconds cleaning up your code after accepting it, it would be great and surely improve overall code history.
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -2,4 +2,4 @@
|
||||
|
||||
github: JustArchi
|
||||
patreon: JustArchi
|
||||
custom: ["https://paypal.me/JustArchi", "https://pay.revolut.com/profile/ukaszyxm", "https://commerce.coinbase.com/checkout/0c23b844-c51b-45f4-9135-8db7c6fcf98e", "https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_"]
|
||||
custom: ["https://paypal.me/JustArchi", "https://pay.revolut.com/justarchi", "https://commerce.coinbase.com/checkout/0c23b844-c51b-45f4-9135-8db7c6fcf98e", "https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_"]
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
1
.github/ISSUE_TEMPLATE/Bug-report.yml
vendored
@@ -85,6 +85,7 @@ body:
|
||||
description: |
|
||||
Every command or action that happened after launching ASF, which leads to the bug.
|
||||
If launching ASF with provided configs (below) is everything that is needed, then this section is not mandatory.
|
||||
Screenshots of the problem and/or steps leading to it could be very useful in particular.
|
||||
placeholder: |
|
||||
1. Put cup below the machine hosting ASF.
|
||||
2. Send `PUT /Api/Coffee` request selecting latte macchiato.
|
||||
|
||||
4
.github/RELEASE_TEMPLATE.md
vendored
4
.github/RELEASE_TEMPLATE.md
vendored
@@ -1,6 +1,6 @@
|
||||
### Notice
|
||||
|
||||
**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don't consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you'd like to learn more.**
|
||||
**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features and rewritten implementations. If you don't consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you'd like to learn more.**
|
||||
|
||||
---
|
||||
|
||||
@@ -14,4 +14,4 @@ This is automated GitHub deployment, human-readable changelog should be availabl
|
||||
|
||||
ASF is available for free, this release was made possible thanks to the people that decided to support the project. If you're grateful for what we're doing, please consider a donation. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even $1 is highly appreciated and shows that you care. Thank you!
|
||||
|
||||
[](https://github.com/sponsors/JustArchi) [](https://www.patreon.com/JustArchi) [](https://commerce.coinbase.com/checkout/0c23b844-c51b-45f4-9135-8db7c6fcf98e) [](https://paypal.me/JustArchi) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [](https://pay.revolut.com/profile/ukaszyxm) [](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
[](https://github.com/sponsors/JustArchi) [](https://www.patreon.com/JustArchi) [](https://commerce.coinbase.com/checkout/0c23b844-c51b-45f4-9135-8db7c6fcf98e) [](https://paypal.me/JustArchi) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [](https://pay.revolut.com/justarchi) [](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
|
||||
2
.github/SECURITY.md
vendored
2
.github/SECURITY.md
vendored
@@ -18,6 +18,6 @@ We announce security advisories for our program on **[GitHub](https://github.com
|
||||
|
||||
We're doing our best to protect our community from all harm, therefore we take security vulnerabilities very seriously.
|
||||
|
||||
If you believe that you've found one, we'd appreciate if you let us know about it. You can do so by contacting us privately at ASF@JustArchi.net e-mail, where we'll do our best to evaluate your issue ASAP and keep you updated with the development status. If your vulnerability isn't crucial and doesn't result in a direct escalation, therefore can be known publicly while the appropriate fix is being implemented, you can also open a standard **[issue](https://github.com/JustArchiNET/ArchiSteamFarm/issues/new/choose)** instead.
|
||||
If you believe that you've found one, we'd appreciate if you let us know about it. You can do so by **[opening a security advisory](https://github.com/JustArchiNET/ArchiSteamFarm/security/advisories/new)**, where we'll do our best to evaluate your issue ASAP and keep you updated with the development status. If your vulnerability isn't crucial and doesn't result in a direct escalation, therefore can be known publicly while the appropriate fix is being implemented, you can also open a standard **[issue](https://github.com/JustArchiNET/ArchiSteamFarm/issues/new/choose)** instead.
|
||||
|
||||
Depending on the severity of the issue, we might take further actions in order to limit potential damage, for example by speeding up the release of the next stable ASF version. This is evaluated on a case-by-case basis.
|
||||
|
||||
2
.github/SUPPORT.md
vendored
2
.github/SUPPORT.md
vendored
@@ -4,4 +4,4 @@ Our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** is the offic
|
||||
|
||||
We also have three independent support channels dedicated to our ASF users, in case you couldn't manage to solve the issue yourself. We answer all support and technical matters in our **[GitHub discussions](https://github.com/JustArchiNET/ArchiSteamFarm/discussions/categories/support)**, **[Steam group](https://steamcommunity.com/groups/archiasf/discussions/1)**, and on our **[Discord server](https://discord.gg/hSQgt8j)**. You're free to use the support channel that matches your preferences, although keep in mind that you have a higher chance solving your issue on the GitHub or Steam, where we're doing our best to answer all questions that couldn't be answered by our community itself (as opposed to Discord server where we're not active 24/7 and therefore not always able to answer).
|
||||
|
||||
GitHub **issues** (unlike discussions), are being used solely for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub issues is **not** a general support channel, it's dedicated exclusively to ASF development and we're not answering common ASF matters there, as we have appropriate support channels (mentioned above) for that. Common matters include not only general questions or issues that are obviously related to program usage, but also users reporting "bugs" that are clearly considered intended behaviour coming for example (and mainly) from misconfiguration or lack of understanding how the program works. If you're not sure whether your matter relates to ASF development or not, especially if you're not sure if it's a bug or intended behaviour, we recommend to use a support channel instead, where we'll answer you in calm atmosphere and forward your matter to GitHub if deemed appropriate. Invalid GitHub issues will be closed immediately and won't be answered.
|
||||
GitHub **issues** (unlike discussions), are being used solely for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub issues is **not** a general support channel, it's dedicated exclusively to ASF development and we're not answering common ASF matters there, as we have appropriate support channels (mentioned above) for that. Common matters include not only general questions or issues that are obviously related to program usage, but also users reporting "bugs" that are clearly considered intended behaviour coming for example (and mainly) from misconfiguration or lack of understanding how the program works. If you're not sure whether your matter relates to ASF development or not, especially if you're not sure if it's a bug or intended behaviour, we recommend to use a support channel instead, where we'll answer you in calm atmosphere and forward your matter as GitHub issue if deemed appropriate. Invalid GitHub issues will be closed immediately and won't be answered.
|
||||
|
||||
22
.github/crowdin.yml
vendored
22
.github/crowdin.yml
vendored
@@ -1,3 +1,4 @@
|
||||
"base_path": ".."
|
||||
"preserve_hierarchy": true
|
||||
"files": [
|
||||
{
|
||||
@@ -5,7 +6,21 @@
|
||||
"translation": "/ArchiSteamFarm/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx"
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
".zh-HK.resx": ".zh-Hant-HK.resx",
|
||||
".zh-TW.resx": ".zh-Hant.resx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.resx",
|
||||
"translation": "/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
".zh-HK.resx": ".zh-Hant-HK.resx",
|
||||
".zh-TW.resx": ".zh-Hant.resx"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -13,7 +28,10 @@
|
||||
"translation": "/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization/Strings.%locale%.resx",
|
||||
"translation_replace": {
|
||||
".lol-US.resx": ".qps-Ploc.resx",
|
||||
".sr-CS.resx": ".sr-Latn.resx"
|
||||
".sr-CS.resx": ".sr-Latn.resx",
|
||||
".zh-CN.resx": ".zh-Hans.resx",
|
||||
".zh-HK.resx": ".zh-Hant-HK.resx",
|
||||
".zh-TW.resx": ".zh-Hant.resx"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_SDK_VERSION: 6.0.x
|
||||
DOTNET_SDK_VERSION: 7.0.x
|
||||
|
||||
jobs:
|
||||
main:
|
||||
@@ -19,12 +19,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1.9.0
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -38,9 +38,8 @@ jobs:
|
||||
run: dotnet test ArchiSteamFarm.Tests -c "${{ matrix.configuration }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Upload latest strings for translation on Crowdin
|
||||
continue-on-error: true
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
||||
uses: crowdin/github-action@1.4.4
|
||||
uses: crowdin/github-action@1.5.2
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
config: '.github/crowdin.yml'
|
||||
|
||||
6
.github/workflows/docker-ci.yml
vendored
6
.github/workflows/docker-ci.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
|
||||
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
||||
uses: docker/build-push-action@v2.7.0
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
|
||||
10
.github/workflows/docker-publish-latest.yml
vendored
10
.github/workflows/docker-publish-latest.yml
vendored
@@ -15,22 +15,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -55,7 +55,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@v2.7.0
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.Service
|
||||
|
||||
13
.github/workflows/docker-publish-main.yml
vendored
13
.github/workflows/docker-publish-main.yml
vendored
@@ -16,22 +16,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -55,7 +55,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@v2.7.0
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
@@ -70,8 +70,7 @@ jobs:
|
||||
push: true
|
||||
|
||||
- name: Update DockerHub repository description
|
||||
continue-on-error: true
|
||||
uses: peter-evans/dockerhub-description@v2.4.3
|
||||
uses: peter-evans/dockerhub-description@v3.1.2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
10
.github/workflows/docker-publish-released.yml
vendored
10
.github/workflows/docker-publish-released.yml
vendored
@@ -16,22 +16,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -56,7 +56,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@v2.7.0
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
2
.github/workflows/lock-threads.yml
vendored
2
.github/workflows/lock-threads.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Lock inactive threads
|
||||
uses: dessant/lock-threads@v3.0.0
|
||||
uses: dessant/lock-threads@v4.0.0
|
||||
with:
|
||||
issue-inactive-days: 60
|
||||
pr-inactive-days: 60
|
||||
|
||||
255
.github/workflows/publish.yml
vendored
255
.github/workflows/publish.yml
vendored
@@ -7,11 +7,11 @@ env:
|
||||
CONFIGURATION: Release
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_SDK_VERSION: 6.0.x
|
||||
NET_CORE_VERSION: net6.0
|
||||
NET_FRAMEWORK_VERSION: net48
|
||||
DOTNET_SDK_VERSION: 7.0.x
|
||||
NET_CORE_VERSION: net7.0
|
||||
NET_FRAMEWORK_VERSION: net481
|
||||
NODE_JS_VERSION: 'lts/*'
|
||||
STEAM_TOKEN_DUMPER_NAME: ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
||||
PLUGINS: ArchiSteamFarm.OfficialPlugins.ItemsMatcher ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
|
||||
STEAM_TOKEN_DUMPER_TOKEN: ${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}
|
||||
|
||||
jobs:
|
||||
@@ -25,12 +25,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1.9.0
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Setup Node.js with npm
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.5.1
|
||||
with:
|
||||
check-latest: true
|
||||
node-version: ${{ env.NODE_JS_VERSION }}
|
||||
@@ -83,15 +83,35 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
- name: Prepare for publishing on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dotnet restore
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
|
||||
- name: Prepare for publishing on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet restore
|
||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
|
||||
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
shell: sh
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new"
|
||||
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
|
||||
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" ]; then
|
||||
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" > "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new"
|
||||
mv "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs"
|
||||
fi
|
||||
|
||||
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Windows
|
||||
@@ -102,27 +122,83 @@ jobs:
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and ($env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
|
||||
(Get-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs"
|
||||
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and ($env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs" -PathType Leaf)) {
|
||||
(Get-Content "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs"
|
||||
}
|
||||
|
||||
- name: Publish ArchiSteamFarm.OfficialPlugins.SteamTokenDumper for .NET Core
|
||||
run: dotnet publish "${{ env.STEAM_TOKEN_DUMPER_NAME }}" -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -o "out/${{ env.STEAM_TOKEN_DUMPER_NAME }}/${{ env.NET_CORE_VERSION }}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
||||
- name: Publish official plugins on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
MAX_JOBS: 3
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
- name: Publish ArchiSteamFarm.OfficialPlugins.SteamTokenDumper for .NET Framework
|
||||
publish() {
|
||||
dotnet publish "$1" -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${1}/${NET_CORE_VERSION}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
}
|
||||
|
||||
for plugin in $PLUGINS; do
|
||||
publish "$plugin" &
|
||||
|
||||
while [ "$(jobs -p | wc -l)" -ge "$MAX_JOBS" ]; do
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
- name: Publish official plugins on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
run: dotnet publish "${{ env.STEAM_TOKEN_DUMPER_NAME }}" -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_FRAMEWORK_VERSION }}" -o "out/${{ env.STEAM_TOKEN_DUMPER_NAME }}/${{ env.NET_FRAMEWORK_VERSION }}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
||||
env:
|
||||
MAX_JOBS: 2
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
- name: Restore packages in preparation for ArchiSteamFarm publishing
|
||||
run: dotnet restore ArchiSteamFarm -p:ContinuousIntegrationBuild=true --nologo
|
||||
$PublishBlock = {
|
||||
param($plugin, $framework)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
Set-Location "$env:GITHUB_WORKSPACE"
|
||||
|
||||
dotnet publish "$plugin" -c "$env:CONFIGURATION" -f "$framework" -o "out\$plugin\$framework" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --no-restore --nologo
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($plugin in $env:PLUGINS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
foreach ($framework in "$env:NET_CORE_VERSION","$env:NET_FRAMEWORK_VERSION") {
|
||||
Start-Job -Name "$plugin $framework" $PublishBlock -ArgumentList "$plugin","$framework"
|
||||
|
||||
# Limit active jobs in parallel to help with memory usage
|
||||
$jobs = $(Get-Job -State Running)
|
||||
|
||||
while (@($jobs).Count -ge $env:MAX_JOBS) {
|
||||
Wait-Job -Job $jobs -Any | Out-Null
|
||||
|
||||
$jobs = $(Get-Job -State Running)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Get-Job | Receive-Job -Wait
|
||||
|
||||
- name: Publish ArchiSteamFarm on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
MAX_JOBS: 3
|
||||
VARIANTS: generic linux-arm linux-arm64 linux-x64 osx-arm64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
|
||||
shell: sh
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
set -euo pipefail
|
||||
|
||||
publish() {
|
||||
if [ "$1" = 'generic' ]; then
|
||||
@@ -133,22 +209,13 @@ jobs:
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${1}" "-p:ASFVariant=$1" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
|
||||
|
||||
# If we're including any overlay for this variant, copy it to output directory
|
||||
variant_os="$(echo "$1" | cut -d '-' -f 1)"
|
||||
|
||||
if [ -d "ArchiSteamFarm/overlay/${variant_os}" ]; then
|
||||
cp -pR "ArchiSteamFarm/overlay/${variant_os}/"* "out/${1}"
|
||||
fi
|
||||
|
||||
if [ "$1" != "$variant_os" ] && [ -d "ArchiSteamFarm/overlay/${1}" ]; then
|
||||
cp -pR "ArchiSteamFarm/overlay/${1}/"* "out/${1}"
|
||||
fi
|
||||
|
||||
# If we're including SteamTokenDumper plugin for this framework, copy it to output directory
|
||||
if [ -d "out/${STEAM_TOKEN_DUMPER_NAME}/${NET_CORE_VERSION}" ]; then
|
||||
mkdir -p "out/${1}/plugins/${STEAM_TOKEN_DUMPER_NAME}"
|
||||
cp -pR "out/${STEAM_TOKEN_DUMPER_NAME}/${NET_CORE_VERSION}/"* "out/${1}/plugins/${STEAM_TOKEN_DUMPER_NAME}"
|
||||
fi
|
||||
# If we're including official plugins for this framework, copy them to output directory
|
||||
for plugin in $PLUGINS; do
|
||||
if [ -d "out/${plugin}/${NET_CORE_VERSION}" ]; then
|
||||
mkdir -p "out/${1}/plugins/${plugin}"
|
||||
cp -pR "out/${plugin}/${NET_CORE_VERSION}/"* "out/${1}/plugins/${plugin}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Include .ico file for all platforms, since only Windows script can bundle it inside the exe
|
||||
cp "resources/ASF.ico" "out/${1}/ArchiSteamFarm.ico"
|
||||
@@ -177,7 +244,7 @@ jobs:
|
||||
# Create the final zip file
|
||||
case "$(uname -s)" in
|
||||
"Darwin")
|
||||
# We prefer to use zip on OS X as 7z implementation on that OS doesn't handle file permissions (chmod +x)
|
||||
# We prefer to use zip on macOS as 7z implementation on that OS doesn't handle file permissions (chmod +x)
|
||||
if command -v zip >/dev/null; then
|
||||
(
|
||||
cd "${GITHUB_WORKSPACE}/out/${1}"
|
||||
@@ -206,20 +273,20 @@ jobs:
|
||||
esac
|
||||
}
|
||||
|
||||
jobs=""
|
||||
|
||||
for variant in $VARIANTS; do
|
||||
publish "$variant" &
|
||||
jobs="$jobs $!"
|
||||
|
||||
while [ "$(jobs -p | wc -l)" -ge "$MAX_JOBS" ]; do
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
for job in $jobs; do
|
||||
wait "$job"
|
||||
done
|
||||
wait
|
||||
|
||||
- name: Publish ArchiSteamFarm on Windows
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
env:
|
||||
MAX_JOBS: 2
|
||||
VARIANTS: generic generic-netf linux-arm linux-arm64 linux-x64 osx-arm64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -254,24 +321,15 @@ jobs:
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
# If we're including any overlay for this variant, copy it to output directory
|
||||
$variant_os = $variant.Split('-', 2)[0];
|
||||
# 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\$targetFramework" -PathType Container) {
|
||||
if (!(Test-Path "out\$variant\plugins\$plugin" -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path "out\$variant\plugins\$plugin" > $null
|
||||
}
|
||||
|
||||
if (Test-Path "ArchiSteamFarm\overlay\$variant_os" -PathType Container) {
|
||||
Copy-Item "ArchiSteamFarm\overlay\$variant_os\*" "out\$variant" -Recurse
|
||||
}
|
||||
|
||||
if (($variant -ne $variant_os) -and (Test-Path "ArchiSteamFarm\overlay\$variant" -PathType Container)) {
|
||||
Copy-Item "ArchiSteamFarm\overlay\$variant\*" "out\$variant" -Recurse
|
||||
}
|
||||
|
||||
# If we're including SteamTokenDumper plugin for this framework, copy it to output directory
|
||||
if (Test-Path "out\$env:STEAM_TOKEN_DUMPER_NAME\$targetFramework" -PathType Container) {
|
||||
if (!(Test-Path "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" > $null
|
||||
Copy-Item "out\$plugin\$targetFramework\*" "out\$variant\plugins\$plugin" -Recurse
|
||||
}
|
||||
|
||||
Copy-Item "out\$env:STEAM_TOKEN_DUMPER_NAME\$targetFramework\*" "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" -Recurse
|
||||
}
|
||||
|
||||
# Icon is available only in .NET Framework and .NET Core Windows build, we'll bundle the .ico file for other flavours
|
||||
@@ -329,7 +387,7 @@ jobs:
|
||||
# Limit active jobs in parallel to help with memory usage
|
||||
$jobs = $(Get-Job -State Running)
|
||||
|
||||
while (@($jobs).Count -ge 5) {
|
||||
while (@($jobs).Count -ge $env:MAX_JOBS) {
|
||||
Wait-Job -Job $jobs -Any | Out-Null
|
||||
|
||||
$jobs = $(Get-Job -State Running)
|
||||
@@ -339,58 +397,50 @@ jobs:
|
||||
Get-Job | Receive-Job -Wait
|
||||
|
||||
- name: Upload ASF-generic
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-generic
|
||||
path: out/ASF-generic.zip
|
||||
|
||||
- name: Upload ASF-generic-netf
|
||||
continue-on-error: true
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-generic-netf
|
||||
path: out/ASF-generic-netf.zip
|
||||
|
||||
- name: Upload ASF-linux-arm
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-arm
|
||||
path: out/ASF-linux-arm.zip
|
||||
|
||||
- name: Upload ASF-linux-arm64
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-arm64
|
||||
path: out/ASF-linux-arm64.zip
|
||||
|
||||
- name: Upload ASF-linux-x64
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-x64
|
||||
path: out/ASF-linux-x64.zip
|
||||
|
||||
- name: Upload ASF-osx-arm64
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-osx-arm64
|
||||
path: out/ASF-osx-arm64.zip
|
||||
|
||||
- name: Upload ASF-osx-x64
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-osx-x64
|
||||
path: out/ASF-osx-x64.zip
|
||||
|
||||
- name: Upload ASF-win-x64
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-win-x64
|
||||
path: out/ASF-win-x64.zip
|
||||
@@ -402,61 +452,58 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
|
||||
# TODO: It'd be perfect if we could match final artifacts to the platform they target, so e.g. linux build comes from the linux machine
|
||||
# However, that is currently impossible due to https://github.com/dotnet/msbuild/issues/3897
|
||||
# Therefore, we'll (sadly) pull artifacts from Windows machine only for now
|
||||
- name: Download ASF-generic artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-generic artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-generic
|
||||
name: ubuntu-latest_ASF-generic
|
||||
path: out
|
||||
|
||||
- name: Download ASF-generic-netf artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-generic-netf
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-linux-arm artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-linux-arm
|
||||
name: ubuntu-latest_ASF-linux-arm
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-linux-arm64
|
||||
name: ubuntu-latest_ASF-linux-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-x64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-linux-x64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-linux-x64
|
||||
name: ubuntu-latest_ASF-linux-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-arm64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-osx-arm64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-osx-arm64
|
||||
name: macos-latest_ASF-osx-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-x64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
- name: Download ASF-osx-x64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-osx-x64
|
||||
name: macos-latest_ASF-osx-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-win-x64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v2.1.0
|
||||
uses: actions/download-artifact@v3.0.1
|
||||
with:
|
||||
name: windows-latest_ASF-win-x64
|
||||
path: out
|
||||
|
||||
- name: Import GPG key for signing
|
||||
uses: crazy-max/ghaction-import-gpg@v4.1.0
|
||||
uses: crazy-max/ghaction-import-gpg@v5.2.0
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||
|
||||
@@ -473,15 +520,13 @@ jobs:
|
||||
)
|
||||
|
||||
- name: Upload SHA512SUMS
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: SHA512SUMS
|
||||
path: out/SHA512SUMS
|
||||
|
||||
- name: Upload SHA512SUMS.sign
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
with:
|
||||
name: SHA512SUMS.sign
|
||||
path: out/SHA512SUMS.sign
|
||||
|
||||
8
.github/workflows/translations.yml
vendored
8
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.4.0
|
||||
uses: actions/checkout@v3.2.0
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git reset --hard origin/master
|
||||
|
||||
- name: Download latest translations from Crowdin
|
||||
uses: crowdin/github-action@1.4.4
|
||||
uses: crowdin/github-action@1.5.2
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
|
||||
|
||||
- name: Import GPG key for signing
|
||||
uses: crazy-max/ghaction-import-gpg@v4.1.0
|
||||
uses: crazy-max/ghaction-import-gpg@v5.2.0
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
|
||||
git_config_global: true
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
git add -A "ArchiSteamFarm/Localization" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization" "wiki"
|
||||
git add -A "ArchiSteamFarm/Localization" "ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/Localization" "wiki"
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "Automatic translations update"
|
||||
|
||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -7,6 +7,9 @@
|
||||
# Ignore all files in custom in-tree config directory (if exists)
|
||||
ArchiSteamFarm/config
|
||||
|
||||
# Ignore all files in custom in-tree www directory (if exists)
|
||||
ArchiSteamFarm/www
|
||||
|
||||
# Ignore private SNK key (if exists)
|
||||
resources/ArchiSteamFarm.snk
|
||||
|
||||
@@ -18,13 +21,16 @@ ArchiSteamFarm/logs
|
||||
# Ignore standard out folders for publishing
|
||||
**/out
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
|
||||
# _ _
|
||||
# | | (_) _ __ _ _ __ __
|
||||
# | | | || '_ \ | | | |\ \/ /
|
||||
# | |___ | || | | || |_| | > <
|
||||
# |_____||_||_| |_| \__,_|/_/\_\
|
||||
#
|
||||
# https://github.com/github/gitignore/blob/master/Global/Linux.gitignore
|
||||
# https://github.com/github/gitignore/blob/main/Global/Linux.gitignore
|
||||
# 4f7062e132d7f88e68ab737e64fef872bd3a491f
|
||||
|
||||
*~
|
||||
@@ -47,7 +53,7 @@ ArchiSteamFarm/logs
|
||||
# | | | | | || (_| || (__ | |_| | ___) |
|
||||
# |_| |_| |_| \__,_| \___| \___/ |____/
|
||||
#
|
||||
# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
|
||||
# 2bb963b16a1957c865335e53537036c2e97399b5
|
||||
|
||||
# General
|
||||
@@ -84,7 +90,7 @@ Temporary Items
|
||||
# |_| |_| \___/ |_| |_| \___/ |____/ \___| \_/ \___||_| \___/ | .__/
|
||||
# |_|
|
||||
#
|
||||
# https://github.com/github/gitignore/blob/master/Global/MonoDevelop.gitignore
|
||||
# https://github.com/github/gitignore/blob/main/Global/MonoDevelop.gitignore
|
||||
# e8b2e1a9cc7c9ca49bb05c20a4c4491b85feba6d
|
||||
|
||||
#User Specific
|
||||
@@ -102,13 +108,13 @@ test-results/
|
||||
# \ V / | |\__ \| |_| || (_| || | ___) || |_ | |_| || (_| || || (_) |
|
||||
# \_/ |_||___/ \__,_| \__,_||_||____/ \__| \__,_| \__,_||_| \___/
|
||||
#
|
||||
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
# 888439ee893d0097862f1d510585bd0e3cfd500f
|
||||
# https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
# 491040e88a572d300a59484cb78c86c5e944b70a
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
@@ -313,9 +319,6 @@ PublishScripts/
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Nuget personal access tokens and Credentials
|
||||
nuget.config
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
@@ -404,6 +407,17 @@ node_modules/
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
@@ -460,6 +474,9 @@ ASALocalRun/
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
@@ -491,7 +508,6 @@ FodyWeavers.xsd
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# __ __ _ _
|
||||
@@ -500,7 +516,7 @@ FodyWeavers.xsd
|
||||
# \ V V / | || | | || (_| || (_) |\ V V / \__ \
|
||||
# \_/\_/ |_||_| |_| \__,_| \___/ \_/\_/ |___/
|
||||
#
|
||||
# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
|
||||
# 5808b77453dec299d4daf8557b05a80be832a5b8
|
||||
|
||||
# Windows thumbnail cache files
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 8b16b79e96...b4186d388f
@@ -11,8 +11,10 @@
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||
<PackageReference Include="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<!-- 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>
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
|
||||
@@ -34,32 +32,13 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
internal static class CatAPI {
|
||||
private const string URL = "https://aws.random.cat";
|
||||
|
||||
internal static async Task<string?> GetRandomCatURL(WebBrowser webBrowser) {
|
||||
internal static async Task<Uri?> GetRandomCatURL(WebBrowser webBrowser) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
Uri request = new($"{URL}/meow");
|
||||
|
||||
ObjectResponse<MeowResponse>? response = await webBrowser.UrlGetToJsonObject<MeowResponse>(request).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(response.Content.Link)) {
|
||||
throw new InvalidOperationException(nameof(response.Content.Link));
|
||||
}
|
||||
|
||||
return Uri.EscapeDataString(response.Content.Link);
|
||||
return response?.Content?.URL;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
private sealed class MeowResponse {
|
||||
[JsonProperty(PropertyName = "file", Required = Required.Always)]
|
||||
internal readonly string Link = "";
|
||||
|
||||
[JsonConstructor]
|
||||
private MeowResponse() { }
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
}
|
||||
|
||||
@@ -38,15 +38,15 @@ public sealed class CatController : ArchiController {
|
||||
/// Fetches URL of a random cat picture.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse<Uri>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> CatGet() {
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
string? link = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
|
||||
Uri? url = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
|
||||
|
||||
return !string.IsNullOrEmpty(link) ? Ok(new GenericResponse<string>(link)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
|
||||
return url != null ? Ok(new GenericResponse<Uri>(url)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
@@ -41,7 +40,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
// Your plugin class should inherit the plugin interfaces it wants to handle
|
||||
// If you do not want to handle a particular action (e.g. OnBotMessage that is offered in IBotMessage), it's the best idea to not inherit it at all
|
||||
// This will keep your code compact, efficient and less dependent. You can always add additional interfaces when you'll need them, this example project will inherit quite a bit of them to show you potential usage
|
||||
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
|
||||
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
|
||||
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
|
||||
// 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
|
||||
public string Name => nameof(ExamplePlugin);
|
||||
@@ -86,16 +85,16 @@ internal sealed class ExamplePlugin : IASF, IBot, IBotCommand, IBotConnection, I
|
||||
// Since ASF already had to do initial parsing in order to determine that the command is unknown, args[] are splitted using standard ASF delimiters
|
||||
// If by any chance you want to handle message in its raw format, you also have it available, although for usual ASF pattern you can most likely stick with args[] exclusively. The message has CommandPrefix already stripped for your convenience
|
||||
// If you do not recognize the command, just return null/empty and allow ASF to gracefully return "unknown command" to user on usual basis
|
||||
public async Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
|
||||
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||
// In comparison with OnBotMessage(), we're using asynchronous CatAPI call here, so we declare our method as async and return the message as usual
|
||||
// Notice how we handle access here as well, it'll work only for FamilySharing+
|
||||
switch (args[0].ToUpperInvariant()) {
|
||||
case "CAT" when bot.HasAccess(steamID, BotConfig.EAccess.FamilySharing):
|
||||
case "CAT" when access >= EAccess.FamilySharing:
|
||||
// Notice how we can decide whether to use bot's AWH WebBrowser or ASF's one. For Steam-related requests, AWH's one should always be used, for third-party requests like those it doesn't really matter
|
||||
// Still, it makes sense to pass AWH's one, so in case you get some errors or alike, you know from which bot instance they come from. It's similar to using Bot's ArchiLogger compared to ASF's one
|
||||
string? randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
|
||||
Uri? randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
|
||||
|
||||
return !string.IsNullOrEmpty(randomCatURL) ? randomCatURL : "God damn it, we're out of cats, care to notify my master? Thanks!";
|
||||
return randomCatURL != null ? randomCatURL.ToString() : "God damn it, we're out of cats, care to notify my master? Thanks!";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
37
ArchiSteamFarm.CustomPlugins.ExamplePlugin/MeowResponse.cs
Normal file
37
ArchiSteamFarm.CustomPlugins.ExamplePlugin/MeowResponse.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
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("file", Required = Required.Always)]
|
||||
internal readonly Uri URL = null!;
|
||||
|
||||
[JsonConstructor]
|
||||
private MeowResponse() { }
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
@@ -9,6 +9,11 @@
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<!-- 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>
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
|
||||
using System;
|
||||
using System.Composition;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.PeriodicGC;
|
||||
|
||||
[Export(typeof(IPlugin))]
|
||||
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||
[UsedImplicitly]
|
||||
internal sealed class PeriodicGCPlugin : IPlugin {
|
||||
private const byte GCPeriod = 60; // In seconds
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<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'">
|
||||
<!-- Madness is already included in netf build of ASF, so we don't need to emit it ourselves -->
|
||||
<PackageReference Update="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Localization\Strings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Localization\Strings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Strings.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
24
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/AssemblyInfo.cs
Normal file
24
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/AssemblyInfo.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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;
|
||||
|
||||
[assembly: CLSCompliant(false)]
|
||||
119
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs
Normal file
119
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Backend.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Net;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
internal static class Backend {
|
||||
internal static async Task<HttpStatusCode?> AnnounceForListing(Bot bot, IReadOnlyList<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
throw new ArgumentNullException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
Uri request = new(ArchiNet.URL, "/Api/Listing/Announce");
|
||||
|
||||
AnnouncementRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), bot.SteamID, tradeToken, inventory, acceptedMatchableTypes, bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything), ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration, nickname, avatarHash);
|
||||
|
||||
BasicResponse? response = await bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
|
||||
|
||||
return response?.StatusCode;
|
||||
}
|
||||
|
||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, string tradeToken) {
|
||||
if (licenseID == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(licenseID));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
throw new ArgumentNullException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
Uri request = new(ArchiNet.URL, "/Api/Listing/Inventories");
|
||||
|
||||
Dictionary<string, string> headers = new(1, StringComparer.Ordinal) {
|
||||
{ "X-License-Key", licenseID.ToString("N") }
|
||||
};
|
||||
|
||||
InventoriesRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), bot.SteamID, tradeToken, inventory, acceptedMatchableTypes, ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration);
|
||||
|
||||
ObjectResponse<GenericResponse<ImmutableHashSet<ListedUser>>>? response = await bot.ArchiWebHandler.WebBrowser.UrlPostToJsonObject<GenericResponse<ImmutableHashSet<ListedUser>>, InventoriesRequest>(request, headers, data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet<ListedUser>.Empty);
|
||||
}
|
||||
|
||||
internal static async Task<HttpStatusCode?> HeartBeatForListing(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
Uri request = new(ArchiNet.URL, "/Api/Listing/HeartBeat");
|
||||
|
||||
HeartBeatRequest data = new(ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid(), bot.SteamID);
|
||||
|
||||
BasicResponse? response = await bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
|
||||
|
||||
return response?.StatusCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Linq;
|
||||
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;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly Guid Guid;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<AssetForListing> Inventory;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly bool MatchEverything;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly byte MaxTradeHoldDuration;
|
||||
|
||||
[JsonProperty]
|
||||
private readonly string? Nickname;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ulong SteamID;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly string TradeToken;
|
||||
|
||||
internal AnnouncementRequest(Guid guid, ulong steamID, string tradeToken, IReadOnlyList<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes, bool matchEverything, byte maxTradeHoldDuration, string? nickname = null, string? avatarHash = null) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(matchableTypes));
|
||||
}
|
||||
|
||||
uint index = 0;
|
||||
|
||||
Guid = guid;
|
||||
SteamID = steamID;
|
||||
TradeToken = tradeToken;
|
||||
Inventory = inventory.Select(asset => new AssetForListing(asset, index++)).ToImmutableHashSet();
|
||||
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
||||
MatchEverything = matchEverything;
|
||||
MaxTradeHoldDuration = maxTradeHoldDuration;
|
||||
|
||||
Nickname = nickname;
|
||||
AvatarHash = avatarHash;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeAvatarHash() => !string.IsNullOrEmpty(AvatarHash);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeNickname() => !string.IsNullOrEmpty(Nickname);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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 ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class AssetForListing : AssetInInventory {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly uint Index;
|
||||
|
||||
internal AssetForListing(Asset asset, uint index) : base(asset) {
|
||||
ArgumentNullException.ThrowIfNull(asset);
|
||||
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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 ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AssetInInventory {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly uint Amount;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong AssetID;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong ClassID;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Asset.ERarity Rarity;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly uint RealAppID;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly bool Tradable;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Asset.EType Type;
|
||||
|
||||
internal AssetInInventory(Asset asset) {
|
||||
ArgumentNullException.ThrowIfNull(asset);
|
||||
|
||||
AssetID = asset.AssetID;
|
||||
Amount = asset.Amount;
|
||||
|
||||
ClassID = asset.ClassID;
|
||||
Tradable = asset.Tradable;
|
||||
|
||||
RealAppID = asset.RealAppID;
|
||||
Type = asset.Type;
|
||||
Rarity = asset.Rarity;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private AssetInInventory() { }
|
||||
|
||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, tradable: Tradable, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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 Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class HeartBeatRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
|
||||
internal HeartBeatRequest(Guid guid, ulong steamID) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
Guid = guid;
|
||||
SteamID = steamID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Linq;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class InventoriesRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetInInventory> Inventory;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly byte MaxTradeHoldDuration;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly string TradeToken;
|
||||
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, string tradeToken, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes, byte maxTradeHoldDuration) {
|
||||
if (guid == Guid.Empty) {
|
||||
throw new ArgumentOutOfRangeException(nameof(guid));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if ((matchableTypes == null) || (matchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(matchableTypes));
|
||||
}
|
||||
|
||||
Guid = guid;
|
||||
SteamID = steamID;
|
||||
TradeToken = tradeToken;
|
||||
Inventory = inventory.Select(static asset => new AssetInInventory(asset)).ToImmutableHashSet();
|
||||
MatchableTypes = matchableTypes.ToImmutableHashSet();
|
||||
MaxTradeHoldDuration = maxTradeHoldDuration;
|
||||
}
|
||||
}
|
||||
@@ -21,47 +21,44 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class ResponseData {
|
||||
internal sealed class ListedUser {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetInInventory> Assets = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes = ImmutableHashSet<Asset.EType>.Empty;
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(PropertyName = "data", Required = Required.DisallowNull)]
|
||||
internal readonly InternalData? Data;
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly bool MatchEverything;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(PropertyName = "success", Required = Required.Always)]
|
||||
internal readonly bool Success;
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly byte MaxTradeHoldDuration;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
internal readonly string? Nickname;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly string TradeToken = "";
|
||||
|
||||
[JsonConstructor]
|
||||
private ResponseData() { }
|
||||
|
||||
internal sealed class InternalData {
|
||||
[JsonProperty(PropertyName = "new_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(PropertyName = "new_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(PropertyName = "new_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(PropertyName = "verified_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(PropertyName = "verified_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(PropertyName = "verified_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonConstructor]
|
||||
private InternalData() { }
|
||||
}
|
||||
private ListedUser() { }
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
@@ -0,0 +1,116 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
[Export(typeof(IPlugin))]
|
||||
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotIdentity, IBotModules, IBotUserNotifications {
|
||||
private static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
|
||||
|
||||
[JsonProperty]
|
||||
public override string Name => nameof(ItemsMatcherPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
public override Version Version => typeof(ItemsMatcherPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public async Task OnBotDestroy(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnBotInit(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (bot.BotConfig.RemoteCommunication == BotConfig.ERemoteCommunication.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
remoteCommunication = new RemoteCommunication(bot);
|
||||
|
||||
if (!RemoteCommunications.TryAdd(bot, remoteCommunication)) {
|
||||
await remoteCommunication.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<UserNotificationsCallback.EUserNotification> newNotifications) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if ((newNotifications == null) || (newNotifications.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(newNotifications));
|
||||
}
|
||||
|
||||
// We're interested only in Items notification for Bot that has RemoteCommunication enabled
|
||||
if (!newNotifications.Contains(UserNotificationsCallback.EUserNotification.Items) || !RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
remoteCommunication.OnNewItemsNotification();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task OnLoaded() {
|
||||
Utilities.WarnAboutIncompleteTranslation(Strings.ResourceManager);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnSelfPersonaState(Bot bot, SteamFriends.PersonaStateCallback data, string? nickname, string? avatarHash) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (!RemoteCommunications.TryGetValue(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
// This bot doesn't have RemoteCommunication enabled
|
||||
return;
|
||||
}
|
||||
|
||||
await remoteCommunication.OnPersonaState(nickname, avatarHash).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
This directory contains ASF strings for display and localization purposes.
|
||||
|
||||
All strings used by ASF can be found in main `Strings.resx` file, and that's also the only `resx` file that should be modified - all other `resx` files are managed automatically and should not be touched. Please visit **[localization](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Localization)** section on the wiki if you want to improve translation of other files.
|
||||
72
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.Designer.cs
generated
Normal file
72
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/Localization/Strings.Designer.cs
generated
Normal file
@@ -0,0 +1,72 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization {
|
||||
using System;
|
||||
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Strings {
|
||||
|
||||
private static System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Strings() {
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.Equals(null, resourceMan)) {
|
||||
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization.Strings", typeof(Strings).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ActivelyMatchingItemsRound {
|
||||
get {
|
||||
return ResourceManager.GetString("ActivelyMatchingItemsRound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ListingAnnouncing {
|
||||
get {
|
||||
return ResourceManager.GetString("ListingAnnouncing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string MatchingFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MatchingFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string TradeOfferFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("TradeOfferFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,45 @@
|
||||
<?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:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root" xmlns="">
|
||||
<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: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: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: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: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: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:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -62,34 +62,20 @@
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Matched a total of {0} sets this round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="ListingAnnouncing" xml:space="preserve">
|
||||
<value>Announcing {0} ({1}) with inventory made out of {2} items in total on the listing...</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>Matched a total of {0} items with bot {1} ({2}), sending trade offer...</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>Failed to send a trade offer to bot {0} ({1}), moving on...</value>
|
||||
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -28,27 +28,25 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Cards;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Exchange;
|
||||
using ArchiSteamFarm.Steam.Integration;
|
||||
using ArchiSteamFarm.Steam.Security;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
|
||||
namespace ArchiSteamFarm.Core;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
internal sealed class Statistics : IAsyncDisposable {
|
||||
private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays
|
||||
private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory
|
||||
private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch
|
||||
private const byte MinAnnouncementCheckTTL = 6; // Minimum amount of hours we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL
|
||||
internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
private const byte MaxAnnouncementTTL = 60; // Maximum amount of minutes we can wait before the next Announcement
|
||||
private const byte MinAnnouncementTTL = 5; // Minimum amount of minutes we must wait before the next Announcement
|
||||
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
|
||||
private const byte MinItemsCount = 100; // Minimum amount of items to be eligible for public listing
|
||||
private const byte MinPersonaStateTTL = 8; // Minimum amount of hours we must wait before requesting persona state update
|
||||
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
|
||||
|
||||
private static readonly ImmutableHashSet<Asset.EType> AcceptedMatchableTypes = ImmutableHashSet.Create(
|
||||
Asset.EType.Emoticon,
|
||||
@@ -58,87 +56,85 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer? HeartBeatTimer;
|
||||
private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1);
|
||||
|
||||
#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
private readonly Timer MatchActivelyTimer;
|
||||
#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
|
||||
private readonly Timer? MatchActivelyTimer;
|
||||
private readonly SemaphoreSlim RequestsSemaphore = new(1, 1);
|
||||
|
||||
private DateTime LastAnnouncementCheck;
|
||||
private DateTime LastAnnouncement;
|
||||
private DateTime LastHeartBeat;
|
||||
private DateTime LastPersonaStateRequest;
|
||||
private bool ShouldSendAnnouncementEarlier;
|
||||
private bool ShouldSendHeartBeats;
|
||||
|
||||
internal Statistics(Bot bot) {
|
||||
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
internal RemoteCommunication(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
MatchActivelyTimer = new Timer(
|
||||
MatchActively,
|
||||
null,
|
||||
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
|
||||
TimeSpan.FromHours(8) // Period
|
||||
);
|
||||
Bot = bot;
|
||||
|
||||
if (Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
|
||||
HeartBeatTimer = new Timer(
|
||||
HeartBeat,
|
||||
null,
|
||||
TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0),
|
||||
TimeSpan.FromMinutes(1)
|
||||
);
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively)) {
|
||||
if ((ASF.GlobalConfig?.LicenseID != null) && (ASF.GlobalConfig.LicenseID != Guid.Empty)) {
|
||||
MatchActivelyTimer = new Timer(
|
||||
MatchActively,
|
||||
null,
|
||||
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0),
|
||||
TimeSpan.FromHours(6)
|
||||
);
|
||||
} else {
|
||||
bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningNoLicense, nameof(BotConfig.ETradingPreferences.MatchActively)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
MatchActivelySemaphore.Dispose();
|
||||
RequestsSemaphore.Dispose();
|
||||
|
||||
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
HeartBeatTimer?.Dispose();
|
||||
MatchActivelyTimer?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task OnHeartBeat() {
|
||||
// Request persona update if needed
|
||||
if ((DateTime.UtcNow > LastPersonaStateRequest.AddHours(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))) {
|
||||
LastPersonaStateRequest = DateTime.UtcNow;
|
||||
Bot.RequestPersonaStateUpdate();
|
||||
public async ValueTask DisposeAsync() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
MatchActivelySemaphore.Dispose();
|
||||
RequestsSemaphore.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
if (HeartBeatTimer != null) {
|
||||
await HeartBeatTimer.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await RequestsSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpStatusCode? response = await ArchiNet.HeartBeatForListing(Bot).ConfigureAwait(false);
|
||||
|
||||
if (!response.HasValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Value.IsClientErrorCode()) {
|
||||
LastHeartBeat = DateTime.MinValue;
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
} finally {
|
||||
RequestsSemaphore.Release();
|
||||
if (MatchActivelyTimer != null) {
|
||||
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task OnLoggedOn() {
|
||||
if (!await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false)) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ArchiWebHandler.JoinGroup)));
|
||||
}
|
||||
}
|
||||
internal void OnNewItemsNotification() => ShouldSendAnnouncementEarlier = true;
|
||||
|
||||
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
|
||||
if ((DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
|
||||
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
|
||||
return;
|
||||
}
|
||||
|
||||
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if ((DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
|
||||
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,17 +149,9 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
if (!eligible.Value) {
|
||||
LastAnnouncementCheck = DateTime.UtcNow;
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
// We're not eligible, record this as a valid check
|
||||
LastAnnouncement = DateTime.UtcNow;
|
||||
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -171,70 +159,154 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
|
||||
if (acceptedMatchableTypes.Count == 0) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes));
|
||||
LastAnnouncementCheck = DateTime.UtcNow;
|
||||
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(tradeToken)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Asset> inventory;
|
||||
List<Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)).ToHashSetAsync().ConfigureAwait(false);
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().ToListAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LastAnnouncementCheck = DateTime.UtcNow;
|
||||
|
||||
// This is actual inventory
|
||||
if (inventory.Count < MinItemsCount) {
|
||||
ShouldSendHeartBeats = false;
|
||||
if (inventory.Count(item => item.Tradable && acceptedMatchableTypes.Contains(item.Type)) < MinItemsCount) {
|
||||
// We're not eligible, record this as a valid check
|
||||
LastAnnouncement = DateTime.UtcNow;
|
||||
ShouldSendAnnouncementEarlier = ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.ListingAnnouncing, Bot.SteamID, nickname, inventory.Count));
|
||||
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
HttpStatusCode? response = await ArchiNet.AnnounceForListing(Bot, inventory, acceptedMatchableTypes, tradeToken!, nickname, avatarHash).ConfigureAwait(false);
|
||||
HttpStatusCode? response = await Backend.AnnounceForListing(Bot, inventory, acceptedMatchableTypes, tradeToken!, nickname, avatarHash).ConfigureAwait(false);
|
||||
|
||||
if (!response.HasValue) {
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We've got the response, regardless what happened, we've succeeded in a valid check
|
||||
LastAnnouncement = DateTime.UtcNow;
|
||||
ShouldSendAnnouncementEarlier = false;
|
||||
|
||||
if (response.Value.IsClientErrorCode()) {
|
||||
LastHeartBeat = DateTime.MinValue;
|
||||
// ArchiNet told us that we've sent a bad request, so the process should restart from the beginning at later time
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response));
|
||||
|
||||
switch (response) {
|
||||
case HttpStatusCode.Forbidden:
|
||||
// ArchiNet told us to stop submitting data for now
|
||||
LastAnnouncement = DateTime.UtcNow.AddYears(1);
|
||||
|
||||
break;
|
||||
#if NETFRAMEWORK
|
||||
case (HttpStatusCode) 429:
|
||||
#else
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
#endif
|
||||
|
||||
// ArchiNet told us to try again later
|
||||
LastAnnouncement = DateTime.UtcNow.AddDays(1);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
ShouldSendHeartBeats = true;
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
|
||||
} finally {
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async void HeartBeat(object? state = null) {
|
||||
if (!Bot.IsConnectedAndLoggedOn || (Bot.HeartBeatFailures > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Request persona update if needed
|
||||
if ((DateTime.UtcNow > LastPersonaStateRequest.AddMinutes(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL))) {
|
||||
LastPersonaStateRequest = DateTime.UtcNow;
|
||||
Bot.RequestPersonaStateUpdate();
|
||||
}
|
||||
|
||||
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await RequestsSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpStatusCode? response = await Backend.HeartBeatForListing(Bot).ConfigureAwait(false);
|
||||
|
||||
if (!response.HasValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Value.IsClientErrorCode()) {
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
} finally {
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool?> IsEligibleForListing() {
|
||||
// Bot must be eligible for matching first
|
||||
bool? isEligibleForMatching = await IsEligibleForMatching().ConfigureAwait(false);
|
||||
|
||||
if (isEligibleForMatching != true) {
|
||||
return isEligibleForMatching;
|
||||
}
|
||||
|
||||
// Bot must have STM enabled in TradingPreferences
|
||||
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have public inventory
|
||||
bool? hasPublicInventory = await Bot.HasPublicInventory().ConfigureAwait(false);
|
||||
|
||||
@@ -255,13 +327,6 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have STM enable in TradingPreferences
|
||||
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have at least one accepted matchable type set
|
||||
if ((Bot.BotConfig.MatchableTypes.Count == 0) || Bot.BotConfig.MatchableTypes.All(static type => !AcceptedMatchableTypes.Contains(type))) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.MatchableTypes)}: {string.Join(", ", Bot.BotConfig.MatchableTypes)}"));
|
||||
@@ -282,6 +347,14 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
private async void MatchActively(object? state = null) {
|
||||
if (ASF.GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.GlobalConfig));
|
||||
}
|
||||
|
||||
if (!ASF.GlobalConfig.LicenseID.HasValue || (ASF.GlobalConfig.LicenseID == Guid.Empty)) {
|
||||
throw new InvalidOperationException(nameof(ASF.GlobalConfig.LicenseID));
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || (await IsEligibleForMatching().ConfigureAwait(false) != true)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
|
||||
|
||||
@@ -291,7 +364,7 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
|
||||
if (acceptedMatchableTypes.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
|
||||
Bot.ArchiLogger.LogNullError(acceptedMatchableTypes);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -303,134 +376,122 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
try {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.Starting);
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Starting);
|
||||
|
||||
Dictionary<ulong, (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs)> triedSteamIDs = new();
|
||||
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
|
||||
|
||||
bool shouldContinueMatching = true;
|
||||
bool tradedSomething = false;
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(tradeToken)));
|
||||
|
||||
for (byte i = 0; (i < MaxMatchingRounds) && shouldContinueMatching; i++) {
|
||||
if ((i > 0) && tradedSomething) {
|
||||
// After each round we wait at least 5 minutes for all bots to react
|
||||
await Task.Delay(5 * 60 * 1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!Bot.IsConnectedAndLoggedOn || Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively) || (await IsEligibleForMatching().ConfigureAwait(false) != true)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma warning disable CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
using (await Bot.Actions.GetTradingLock().ConfigureAwait(false)) {
|
||||
#pragma warning restore CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.ActivelyMatchingItems, i));
|
||||
(shouldContinueMatching, tradedSomething) = await MatchActivelyRound(acceptedMatchableTypes, triedSteamIDs).ConfigureAwait(false);
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.DoneActivelyMatchingItems, i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.Done);
|
||||
HashSet<Asset> ourInventory;
|
||||
|
||||
try {
|
||||
ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ourInventory)));
|
||||
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ourInventory)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ourInventory.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(ourInventory)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)? response = await Backend.GetListedUsersForMatching(ASF.GlobalConfig.LicenseID.Value, Bot, ourInventory, acceptedMatchableTypes, tradeToken!).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(response)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.Value.StatusCode.IsSuccessCode()) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.Value.StatusCode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Value.Users.IsEmpty) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(response.Value.Users)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma warning disable CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
using (await Bot.Actions.GetTradingLock().ConfigureAwait(false)) {
|
||||
#pragma warning restore CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Starting);
|
||||
await MatchActivelyRound(response.Value.Users, ourInventory, acceptedMatchableTypes).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Done);
|
||||
} finally {
|
||||
MatchActivelySemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs)> triedSteamIDs) {
|
||||
private async Task MatchActivelyRound(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourInventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
if ((listedUsers == null) || (listedUsers.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(listedUsers));
|
||||
}
|
||||
|
||||
if ((ourInventory == null) || (ourInventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(ourInventory));
|
||||
}
|
||||
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(triedSteamIDs);
|
||||
|
||||
HashSet<Asset> ourInventory;
|
||||
|
||||
try {
|
||||
ourInventory = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return (false, false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
if (ourInventory.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(ourInventory)));
|
||||
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
(Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourInventory);
|
||||
|
||||
if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, $"{nameof(ourFullState)} || {nameof(ourTradableState)}"));
|
||||
|
||||
return (false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ImmutableHashSet<ArchiNet.ListedUser>? listedUsers = await ArchiNet.GetListedUsers(Bot).ConfigureAwait(false);
|
||||
|
||||
if ((listedUsers == null) || (listedUsers.Count == 0)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(listedUsers)));
|
||||
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
byte maxTradeHoldDuration = ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration;
|
||||
byte totalMatches = 0;
|
||||
|
||||
HashSet<ulong> triedSteamIDs = new();
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisRound = new();
|
||||
|
||||
foreach (ArchiNet.ListedUser? listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(static listedUser => listedUser.MatchEverything).ThenByDescending(static listedUser => listedUser.MatchEverything || (listedUser.ItemsCount < MaxItemsForFairBots)).ThenByDescending(static listedUser => listedUser.Score)) {
|
||||
byte maxTradeHoldDuration = ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration;
|
||||
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && !triedSteamIDs.Contains(listedUser.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(static listedUser => listedUser.MatchEverything).ThenBy(static listedUser => listedUser.Assets.Count)) {
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
|
||||
|
||||
if (wantedSets.Count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++totalMatches > MaxMatchedBotsHard) {
|
||||
break;
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericTrace($"{listedUser.SteamID}...");
|
||||
|
||||
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDurationForUser(listedUser.SteamID, listedUser.TradeToken).ConfigureAwait(false);
|
||||
byte? tradeHoldDuration = await Bot.ArchiWebHandler.GetCombinedTradeHoldDurationAgainstUser(listedUser.SteamID, listedUser.TradeToken).ConfigureAwait(false);
|
||||
|
||||
switch (holdDuration) {
|
||||
switch (tradeHoldDuration) {
|
||||
case null:
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(holdDuration)));
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(tradeHoldDuration)));
|
||||
|
||||
continue;
|
||||
case > 0 when holdDuration.Value > maxTradeHoldDuration:
|
||||
Bot.ArchiLogger.LogGenericTrace($"{holdDuration.Value} > {maxTradeHoldDuration}");
|
||||
case > 0 when (tradeHoldDuration.Value > maxTradeHoldDuration) || (tradeHoldDuration.Value > listedUser.MaxTradeHoldDuration):
|
||||
Bot.ArchiLogger.LogGenericTrace($"{tradeHoldDuration.Value} > {maxTradeHoldDuration} || {listedUser.MaxTradeHoldDuration}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
HashSet<Asset> theirInventory;
|
||||
|
||||
try {
|
||||
theirInventory = await Bot.ArchiWebHandler.GetInventoryAsync(listedUser.SteamID).Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((holdDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericException(e);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (theirInventory.Count == 0) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(theirInventory)));
|
||||
|
||||
continue;
|
||||
}
|
||||
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
|
||||
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisUser = new();
|
||||
|
||||
@@ -463,9 +524,9 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
if (inventoryStateChanges.TryGetValue(set, out Dictionary<ulong, uint>? pastChanges) && (pastChanges.Count > 0)) {
|
||||
foreach ((ulong classID, uint amount) in pastChanges) {
|
||||
if (!ourFullSet.TryGetValue(classID, out uint fullAmount) || (fullAmount == 0) || (fullAmount < amount)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(fullAmount));
|
||||
Bot.ArchiLogger.LogNullError(fullAmount);
|
||||
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullAmount > amount) {
|
||||
@@ -475,9 +536,9 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
if (!ourTradableSet.TryGetValue(classID, out uint tradableAmount) || (tradableAmount == 0) || (tradableAmount < amount)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(tradableAmount));
|
||||
Bot.ArchiLogger.LogNullError(tradableAmount);
|
||||
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullAmount > amount) {
|
||||
@@ -597,33 +658,20 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
|
||||
// Remove the items from inventories
|
||||
HashSet<Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive);
|
||||
HashSet<Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive);
|
||||
HashSet<Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive, true);
|
||||
|
||||
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairExchange(itemsToGive, itemsToReceive)) {
|
||||
// Failsafe
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, Strings.ErrorAborted));
|
||||
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) previousAttempt)) {
|
||||
if ((previousAttempt.GivenAssetIDs == null) || (previousAttempt.ReceivedAssetIDs == null) || (itemsToGive.Select(static item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(static item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains))) {
|
||||
// This user didn't respond in our previous round, avoid him for remaining ones
|
||||
triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs);
|
||||
triedSteamIDs.Add(listedUser.SteamID);
|
||||
|
||||
break;
|
||||
}
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.MatchingFound, itemsToReceive.Count, listedUser.SteamID, listedUser.Nickname));
|
||||
|
||||
previousAttempt.GivenAssetIDs.UnionWith(itemsToGive.Select(static item => item.AssetID));
|
||||
previousAttempt.ReceivedAssetIDs.UnionWith(itemsToReceive.Select(static item => item.AssetID));
|
||||
} else {
|
||||
previousAttempt.GivenAssetIDs = new HashSet<ulong>(itemsToGive.Select(static item => item.AssetID));
|
||||
previousAttempt.ReceivedAssetIDs = new HashSet<ulong>(itemsToReceive.Select(static item => item.AssetID));
|
||||
}
|
||||
|
||||
triedSteamIDs[listedUser.SteamID] = (++previousAttempt.Tries, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs);
|
||||
|
||||
Bot.ArchiLogger.LogGenericTrace($"{Bot.SteamID} <- {string.Join(", ", itemsToReceive.Select(static item => $"{item.RealAppID}/{item.Type}-{item.ClassID} #{item.Amount}"))} | {string.Join(", ", itemsToGive.Select(static item => $"{item.RealAppID}/{item.Type}-{item.ClassID} #{item.Amount}"))} -> {listedUser.SteamID}");
|
||||
Bot.ArchiLogger.LogGenericTrace($"{Bot.SteamID} <- {string.Join(", ", itemsToReceive.Select(static item => $"{item.RealAppID}/{item.Type}/{item.Rarity}/{item.ClassID} #{item.Amount}"))} | {string.Join(", ", itemsToGive.Select(static item => $"{item.RealAppID}/{item.Type}/{item.Rarity}/{item.ClassID} #{item.Amount}"))} -> {listedUser.SteamID}");
|
||||
|
||||
(bool success, HashSet<ulong>? mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);
|
||||
|
||||
@@ -631,14 +679,14 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
(bool twoFactorSuccess, _, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
|
||||
|
||||
if (!twoFactorSuccess) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed);
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(twoFactorSuccess)));
|
||||
|
||||
return (false, skippedSetsThisRound.Count > 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.WarningFailed);
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Localization.Strings.TradeOfferFailed, listedUser.SteamID, listedUser.Nickname));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -647,15 +695,10 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
theirInventory.UnionWith(itemsToGive);
|
||||
|
||||
skippedSetsThisUser.UnionWith(skippedSetsThisTrade);
|
||||
Bot.ArchiLogger.LogGenericTrace(Strings.Success);
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
|
||||
}
|
||||
|
||||
if (skippedSetsThisUser.Count == 0) {
|
||||
if (skippedSetsThisRound.Count == 0) {
|
||||
// If we didn't find any match on clean round, this user isn't going to have anything interesting for us anytime soon
|
||||
triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, null, null);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -675,9 +718,6 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
ourTradableState.TrimExcess();
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
|
||||
|
||||
// Keep matching when we either traded something this round (so it makes sense for a refresh) or if we didn't try all available bots yet (so it makes sense to keep going)
|
||||
return ((totalMatches > 0) && ((skippedSetsThisRound.Count > 0) || triedSteamIDs.Values.All(static data => data.Tries < 2)), skippedSetsThisRound.Count > 0);
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Localization.Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,9 @@
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||
<PackageReference Include="JustArchiNET.Madness" IncludeAssets="compile" />
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<!-- 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>
|
||||
|
||||
@@ -27,33 +27,33 @@ using ArchiSteamFarm.Core;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
internal sealed class RequestData {
|
||||
[JsonProperty(PropertyName = "guid", Required = Required.Always)]
|
||||
internal sealed class SubmitRequest {
|
||||
[JsonProperty("guid", Required = Required.Always)]
|
||||
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
|
||||
|
||||
[JsonProperty(PropertyName = "token", Required = Required.Always)]
|
||||
[JsonProperty("token", Required = Required.Always)]
|
||||
private static string Token => SharedInfo.Token;
|
||||
|
||||
[JsonProperty(PropertyName = "v", Required = Required.Always)]
|
||||
[JsonProperty("v", Required = Required.Always)]
|
||||
private static byte Version => SharedInfo.ApiVersion;
|
||||
|
||||
[JsonProperty(PropertyName = "apps", Required = Required.Always)]
|
||||
[JsonProperty("apps", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Apps;
|
||||
|
||||
[JsonProperty(PropertyName = "depots", Required = Required.Always)]
|
||||
[JsonProperty("depots", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Depots;
|
||||
|
||||
private readonly ulong SteamID;
|
||||
|
||||
[JsonProperty(PropertyName = "subs", Required = Required.Always)]
|
||||
[JsonProperty("subs", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Subs;
|
||||
|
||||
[JsonProperty(PropertyName = "steamid", Required = Required.Always)]
|
||||
[JsonProperty("steamid", Required = Required.Always)]
|
||||
private string SteamIDText => new SteamID(SteamID).Render();
|
||||
|
||||
internal RequestData(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
||||
internal SubmitRequest(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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 Newtonsoft.Json;
|
||||
|
||||
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
|
||||
|
||||
#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
|
||||
|
||||
[JsonConstructor]
|
||||
private SubmitResponse() { }
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
@@ -0,0 +1,47 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Collections.Immutable;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
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;
|
||||
|
||||
[JsonProperty("new_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("new_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
@@ -120,7 +120,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
}
|
||||
|
||||
if (globalCache == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(globalCache));
|
||||
ASF.ArchiLogger.LogNullError(globalCache);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
LastChangeNumber = currentChangeNumber;
|
||||
|
||||
foreach ((uint appID, SteamApps.PICSChangesCallback.PICSChangeData appData) in appChanges) {
|
||||
if (!AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) || (appData.ChangeNumber <= previousChangeNumber)) {
|
||||
if (!AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) || (previousChangeNumber >= appData.ChangeNumber)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -170,8 +170,19 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
}
|
||||
|
||||
LastChangeNumber = currentChangeNumber;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
internal void Reset(bool clear = false) {
|
||||
AppChangeNumbers.Clear();
|
||||
|
||||
if (clear) {
|
||||
AppTokens.Clear();
|
||||
DepotKeys.Clear();
|
||||
PackageTokens.Clear();
|
||||
}
|
||||
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
|
||||
@@ -184,7 +195,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
bool save = false;
|
||||
|
||||
foreach ((uint appID, uint changeNumber) in appChangeNumbers) {
|
||||
if (AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) && (previousChangeNumber == changeNumber)) {
|
||||
if (AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) && (previousChangeNumber >= changeNumber)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -281,19 +292,38 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
ArgumentNullException.ThrowIfNull(packages);
|
||||
ArgumentNullException.ThrowIfNull(depots);
|
||||
|
||||
bool save = false;
|
||||
|
||||
foreach ((uint appID, ulong token) in apps) {
|
||||
if (SubmittedApps.TryGetValue(appID, out ulong previousToken) && (previousToken == token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SubmittedApps[appID] = token;
|
||||
save = true;
|
||||
}
|
||||
|
||||
foreach ((uint packageID, ulong token) in packages) {
|
||||
if (SubmittedPackages.TryGetValue(packageID, out ulong previousToken) && (previousToken == token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SubmittedPackages[packageID] = token;
|
||||
save = true;
|
||||
}
|
||||
|
||||
foreach ((uint depotID, string key) in depots) {
|
||||
if (SubmittedDepots.TryGetValue(depotID, out string? previousKey) && (previousKey == key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SubmittedDepots[depotID] = key;
|
||||
save = true;
|
||||
}
|
||||
|
||||
Utilities.InBackground(Save);
|
||||
if (save) {
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidDepotKey(string depotKey) {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?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} быў адключаны з-за адсутнасці токена зборкі</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} зараз адключаны ў адпаведнасці з вашай канфігурацыяй. Калі вы хочаце дапамагчы SteamDB у адпраўцы даных, калі ласка, зазірніце ў нашу вікі.</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} быў паспяхова ініцыялізаваны, загадзя дзякуй за дапамогу. Першая адпраўка адбудзецца прыкладна праз {1}.</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">
|
||||
<value>Не атрымалася загрузіць {0}, будзе ініцыялізаваны новы асобнік...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>На гэтым асобніку бота няма праграм, якія патрабуюць абнаўлення.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Атрыманне ў агульнай складанасці {0} токенаў доступу праграмы...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Атрыманне {0} токенаў доступу праграмы...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Скончана атрыманне {0} токенаў доступу праграмы.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Завершана атрыманне {0} токенаў доступу праграмы.</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>Атрыманне ўсіх сховішч для агульнай колькасці праграм {0}...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Атрыманне інфармацыі пра праграму {0}...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Завершана атрыманне інфармацыі пра праграму {0}.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Атрыманне {0} ключоў сховішча...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Завершана атрыманне {0} ключоў сховішча.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Завершана атрыманне ўсіх ключоў ключоў сховішча для {0} праграм.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Новых дадзеных для адпраўкі няма, усё актуальна.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Памылка адпраўкі дадзеных: карэктны SteamID не быў прадстаўлены. Праверце правільнасць налады {0}.</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>Адпраўка агульнай колькасці зарэгістраваных праграм/пакетаў/сховішч: {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>Адпраўка не ўдалася з-за занадта вялікай колькасці адпраўленых запытаў. Мы паўтарым спробу прыкладна праз {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Дадзеныя былі паспяхова адпраўлены. Сервер зарэгістраваў агульную колькасць новых праграм/пакетаў/сховішч: {0} ({1} праверана)/{2} ({3} праверана)/{4} ({5} праверана).</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>Новыя праграмы: {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>Правераныя праграмы: {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>Новыя пакеты: {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>Правераныя пакеты: {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>Новыя сховішчы: {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>Правераныя сховішчы: {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} ініцыялізаваны, убудова не працуе з ніводным з наступных: {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>Загрузка глабальнага кэша STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Праверка цэласнасці глабальнага кэша STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Не ўдалося праверыць цэласнасць глабальнага кэша STD. Гэта сведчыць аб магчымым пашкоджанні файла/памяці, замест гэтага будзе ініцыялізаваны новы асобнік.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -62,34 +62,119 @@
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||
<value>{0} е била деактивирана поради липсващ токън за изграждане</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} в момента е деактивирана според вашата настройка. Ако искате да помогнете на SteamDB при подаването на данни, моля разгледайте нашата уикипедия.</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} е инициализиранa успешно, благодаря Ви предварително за вашата помощ. Първото подаване на данни ще се случи приблизително след {1}.</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">
|
||||
<value>{0} не беше успешно заредена, вместо това ще бъде стартирана нова инстанция...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Няма нови игри или приложения, които да изискват презареждане на бота.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Събиране на общо {0} входящи токени за игри или приложения...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Събиране на {0} входящи токени за игри или приложения...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Приключи събирането на {0} входящи токени за игри или приложения.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Приключи събиране на общо {0} входящи токени за игри или приложения.</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>Събиране на всички депа за общо {0} игри или проложения...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Събиране на {0} информация за играта или приложението...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Приключи събирането на {0} информация за играта или приложението.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Събиране {0} ключове за депо...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Приключи събирането {0} ключове за депо.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Приключи събирането на всички ключове за депа за общо {0} игри или проложения.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Няма нова информация за подаване, цялата информация е актуална.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Подаването на данните не може да се изпълни, защото няма валиден SteamID зададен като автор или сътрудник. Помислете да настроите {0} правилно.</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>Подаване на общо регистрирани игри/приложения/пакети/депа: {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>Подаването е неуспешно поради твърде много изпратени заявки, ще опитаме отново приблизително след {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Данните са изпратени успешно. Сървърът регистрира общо нови игри/приложения/пакети/депа: {0} ({1} потвърдени)/{2} ({3} потвърдени)/{4} ({5} потвърдени).</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>Нови игри / приложения: {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>Потвърдени игри / приложения: {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>Нови пакети: {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>Потвърдени пакети: {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>Нови депа: {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>Потвърдени депа: {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} стартирана, плъгинът няма да разреши нито една от тези: {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>Зареждане на STD общ кеш...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Потвърждаване на цялостта на STD общия кеш...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Не успя да провери целостта на глобалния STD кеш. Това предполага потенциална грешка, вместо това ще бъде стартирана нова инстанция.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -78,15 +78,29 @@
|
||||
<value>{0} nelze načíst, nová instance bude inicializována...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Neexistují žádné aplikace, které by vyžadovaly aktualizaci této instance bota.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Načítám celkem {0} přístupových tokenů...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Načítání {0} přístupových tokenů...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Načítání {0} přístupových tokenů bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Dokončeno načítání celkem {0} přístupových tokenů.</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>Načítání všech úložišť, celkem z {0} aplikací...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Získávání {0} informací o aplikaci...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
@@ -95,16 +109,45 @@
|
||||
<value>Načítání informací o aplikaci {0} bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Získávání {0} tokenů úložišť...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Načítání {0} přístupových tokenů bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Načítání všech tokenbů úložišť, celkem z {0} aplikací bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Nejsou k dispozici žádné nové údaje k odeslání, vše je aktuální.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Data nelze odeslat, protože neexistuje žádné platné SteamID, které bychom mohli klasifikovat jako přispěvatele. Zvažte nastavení {0} parametrů.</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>Odesílání celkem registrovaných aplikací/balíčků/úložišť: {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>Odeslání se nezdařilo z důvodu příliš mnoha odeslaných požadavků. Pokusíme se znovu přibližně za {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Data byla úspěšně odeslána. Server zaregistroval celkem nové aplikace/balíčky/úložiště: {0} ({1} ověřeno)/{2} ({3} ověřeno)/{4} ({5} ověřeno).</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>Nové aplikace: {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>Ověřené aplikace: {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>Nové balíčky: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
@@ -113,9 +156,18 @@
|
||||
<value>Ověřené balíčky: {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>Nová úložiště: {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>Ověřená úložiště: {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} inicializován, žádný plugin nebude rozpoznávat: {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čítání globální mezipaměti STD...</value>
|
||||
</data>
|
||||
|
||||
@@ -168,7 +168,13 @@
|
||||
<value>{0} wurde initialisiert, das Plugin wird keinen der folgenden Werte verarbeiten: {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>Globaler STD-Cache wird geladen...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Überprüfe STD globale Cache-Integrität...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Fehler beim Überprüfen der globalen STD-Cache-Integrität. Dies deutet auf eine mögliche Datei-/Speicher-Beschädigung hin; stattdessen wird eine neue Instanz initialisiert.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -62,34 +62,119 @@
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||
<value>{0} on poistettu käytöstä puuttuvan koontitunnuksen vuoksi</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} on tällä hetkellä poistettu käytöstä asetuksistasi. Jos haluat auttaa SteamDB:tä tietojen lähettämisessä, ole hyvä ja tutustu wikimme.</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} on alustettu onnistuneesti, kiitos etukäteen avustasi. Ensimmäinen lähetys tapahtuu noin {1} jälkeen.</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">
|
||||
<value>{0} ei voitu ladata. Uusi instanssi alustetaan...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Ei ole sovelluksia, jotka vaatisivat päivitystä tässä botin instanssissa.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Haetaan yhteensä {0} sovelluksen käyttötunnisteita...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Haetaan {0} sovelluksen käyttötunnisteita...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Saatiin haettua {0} sovelluksen käyttötunnisteet.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Saatiin haettua yhteensä {0} sovelluksen käyttötunnisteet.</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>Haetaan kaikkia depotteja yhteensä {0} sovellukselle...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Haetaan {0} sovelluksen tietoja...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Saatiin haettua {0} sovelluksen tiedot.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Haetaan {0} depot-avainta...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Saatiin haettua {0} depot-avainta.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Saatiin haettua kaikki depot-avaimet yhteensä {0} sovellukselle.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Uutta dataa ei ole lähetettäväksi, kaikki on ajan tasalla.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Tietoja ei voitu lähettää, koska ei ole voimassa olevaa SteamID-ryhmää, jonka voisimme luokitella osallistujaksi. Harkitse ominaisuuden {0} asettamista.</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>Lähetetään yhteensä {0}/{1}/{2} rekisteröityjä sovelluksia/paketteja/varikoita...</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>Lähetys epäonnistui liian monen pyynnön vuoksi, yritämme uudelleen noin {0} kuluttua.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Tiedot on lähetetty onnistuneesti. Palvelin on rekisteröinyt yhteensä uusia sovelluksia/paketteja/depoteja: {0} ({1} vahvistettu)/{2} ({3} vahvistettu)/{4} ({5} vahvistettu).</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>Uudet sovellukset: {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>Vahvistetut sovellukset: {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>Uudet paketit: {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>Vahvistetut paketit: {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>Uudet depotit: {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>Vahvistetut depotit: {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} alustettu, laajennus ei käsittele yhtään näistä: {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>Ladataan STD:n globaalia välimuistia...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Tarkistetaan STD-välimuistin eheys...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>STD:n globaalin välimuistin eheyden varmistaminen epäonnistui. Tämä viittaa mahdolliseen tiedoston/muistin korruptioon, uusi instanssi käynnistetään sen sijaan.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -82,14 +82,35 @@
|
||||
|
||||
|
||||
|
||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||
<value>יישומים חדשים: {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>יישומים מאומתים: {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>חבילות חדשות: {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>חבילות מאומתות: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PluginSecretListInitialized" xml:space="preserve">
|
||||
<value>{0} אותחל, הפלאגין לא יפתור אף אחד מאלה: {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>טוען מטמון עולמי מסוג STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>מאמת את שלמות מטמון ה-STD העולמי...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>נכשל אימות שלמות מטמון ה-STD העולמי. זה מרמז על פגיעה פוטנציאלית בקובץ/זיכרון, במקום זה הוא יאותחל מחדש.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -70,28 +70,74 @@
|
||||
<value>A {0} jelenleg ki van kapcsolva a konfigurációid alapján. Ha szeretnéd segíteni a SteamDB-t adatok beküldésével, kérlek nézd meg a wikit.</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<data name="PluginInitializedAndEnabled" xml:space="preserve">
|
||||
<value>A(z) {0} inicializálása sikeresen megtörtént, segítségét előre is köszönjük. Az első beküldés körülbelül {1} múlva fog megtörténni.</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">
|
||||
<value>A(z) {0} nem tölthető be, új példány lesz inicializálva...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Nincsenek olyan alkalmazások, amelyek frissítést igényelnének ezen a bot-példányon.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Összesen {0} alkalmazás-hozzáférési token lekérése...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>{0} alkalmazás-hozzáférési token lekérése...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>{0} alkalmazás-hozzáférési token lekérése befejeződött.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Összesen {0} alkalmazás-hozzáférési token lekérése befejeződött.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>{0} alkalmazás információinak lekérése...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>{0} alkalmazás adatainak lekérése befejeződött.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Nincs új beküldendő adat, minden naprakész.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Nem sikerült elküldeni az adatokat, mert nincs olyan érvényes SteamID, amelyet közreműködőnek minősíthetnénk. Fontolja meg a(z) {0} tulajdon beállítását.</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="SubmissionFailedTooManyRequests" xml:space="preserve">
|
||||
<value>A beküldés meghiúsult, mert túl sok kérés érkezett. Körülbelül {0} múlva újra megpróbáljuk.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||
<value>Új appok: {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>Ellenőrzött alkalmazások: {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>Új csomagok: {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>Ellenőrzött csomagok: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -101,13 +101,22 @@
|
||||
<value>Ricezione di tutti i depositi per un totale di {0} app...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Recuperate {0} informazioni app...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<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="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Recupero {0} chiavi...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Completato il recupero di {0} chiavi.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys 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>
|
||||
@@ -143,11 +152,29 @@
|
||||
<value>Nuovi pacchetti: {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>Pacchetti verificati: {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>Nuove app: {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>App verificate: {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} inizializzato, il plugin non risolverà nessuno dei seguenti: {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>Caricamento cache globale STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Convalida integrità cache globale STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Impossibile verificare l'integrità globale della cache STD. Questo suggerisce un potenziale danneggiamento di file/memoria, una nuova istanza verrà inizializzata.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -63,42 +63,42 @@
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||
<value>{0} foi desativado devido à falta de um token de compilação</value>
|
||||
<value>O {0} foi desativado devido a um token de compilação ausente</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} está desativado de acordo com sua configuração. Se você gostaria de ajudar o SteamDB no envio de dados, por favor, confira nosso wiki.</value>
|
||||
<value>O {0} está desativado de acordo com a sua configuração. Caso deseje ajudar o SteamDB com o envio de informações, dê uma olhada na nossa wiki.</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} foi inicializado com sucesso, obrigado antecipadamente pela sua ajuda. O primeiro envio ocorrerá em aproximadamente {1} a partir de agora.</value>
|
||||
<value>O {0} foi inicializado com sucesso, agradecemos a sua ajuda. O primeiro envio ocorrerá em aproximadamente {1}.</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">
|
||||
<value>{0} não pôde ser carregado, uma instância nova será inicializada...</value>
|
||||
<value>O {0} não pôde ser carregado, uma instância nova será inicializada...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Não há aplicativos que necessitem de ser atualizados nesta instância de bot.</value>
|
||||
<value>Não há aplicativos que exijam atualizações na instância atual.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Recuperando um total de {0} tokens de acesso a aplicativos...</value>
|
||||
<value>Recuperando um total de {0} tokens de acesso para aplicativos...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Recuperando {0} tokens de acesso a aplicativos...</value>
|
||||
<value>Recuperando {0} tokens...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Concluímos a recuperação de {0} tokens de acesso ao aplicativo.</value>
|
||||
<value>Recuperamos {0} tokens.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Obtivemos um total de {0} tokens de acesso aos aplicativos.</value>
|
||||
<value>Recuperamos um total de {0} tokens de acesso.</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>Recuperando todos os depósitos por um total de {0} apps...</value>
|
||||
<value>Recuperando depots para todos os {0} aplicativos...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
@@ -106,38 +106,38 @@
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Concluímos a recuperação de {0} informações de aplicativo.</value>
|
||||
<value>Recuperamos um total de {0} informações de aplicativos.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Recuperando {0} chaves de depósito...</value>
|
||||
<value>Recuperando {0} códigos de depots...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Terminamos de recuperar {0} chaves de depósito.</value>
|
||||
<value>Recuperamos códigos para {0} depots.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Terminamos de recuperar todas as chaves de depósito para um total de {0} apps.</value>
|
||||
<value>Recuperamos códigos de acesso para {0} aplicativos.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Não há novos dados para enviar, tudo está atualizado.</value>
|
||||
<value>Não há novos dados a serem enviados. Tudo está atualizado.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Não foi possível enviar os dados porque não há um conjunto SteamID válido que possamos classificar como colaborador. Considere configurar a propriedade {0}.</value>
|
||||
<value>Não foi possível enviar os dados porque não conseguimos identificar um ID Steam válido para definir como colaborador. Considere configurar a propriedade "{0}".</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>Enviando um total de apps/pacotes/pacotes registrados: {0}/{1}/{2}...</value>
|
||||
<value>Enviando um total de {0} aplicativos, {1} pacotes e {2} depots registrados...</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>O envio falhou devido a muitas solicitações enviadas, tentaremos novamente em aproximadamente {0} a partir de agora.</value>
|
||||
<value>O envio falhou porque muitas solicitações foram enviadas pelo cliente. Tentaremos novamente em aproximadamente {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Os dados foram enviados com sucesso. O servidor registrou um total de novos aplicativos/pacotes/depósitos: {0} ({1} verificado)/{2} ({3} verificado)/{4} ({5} verificado).</value>
|
||||
<value>Os dados foram enviados com sucesso. O servidor registrou um total de {0} aplicativos ({1} verificados), {2} pacotes ({3} verificados) e {4} depots ({5} verificados).</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">
|
||||
@@ -157,24 +157,24 @@
|
||||
<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>Novos depósitos: {0}</value>
|
||||
<value>Novos depots: {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>Depósitos verificados: {0}</value>
|
||||
<value>Depots verificados: {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} inicializado, o plugin não resolverá nenhum desses: {1}.</value>
|
||||
<value>{0} inicializado, o plugin ignorará os seguintes pacotes: {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>Carregando cache STD global...</value>
|
||||
<value>Carregando cache global do STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Validando integridade do cache STD global...</value>
|
||||
<value>Validando integridade do cache global do STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Falha ao verificar a integridade do cache STD global. Isso sugere uma potencial corrupção de arquivo/memória, uma instância nova será inicializada em vez disso.</value>
|
||||
<value>Falha ao verificar a integridade do cache global do STD. Isto pode indicar uma possível corrupção de arquivo/memória, uma nova instância será inicializada.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -168,7 +168,13 @@
|
||||
<value>{0} INITIALIZD, TEH PLUGIN WILL NOT RESOLVE ANY OV DOSE: {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>LOADIN STD GLOBAL CACHE...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>VALIDATIN STD GLOBAL CACHE INTEGRITY...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>FAILD 2 VERIFY STD GLOBAL CACHE INTEGRITY. DIS SUGGESTS POTENTIAL FILE/MEMS CORRUPSHUN, FRESH INSTANCE WILL BE INITIALIZD INSTEAD.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -168,7 +168,13 @@
|
||||
<value>{0} инициализирован, плагин не взаимодействует ни с одним из их: {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>Загрузка глобального кэша STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Проверка целостности глобального кэша STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Не удалось проверить целостность глобального кэша STD. Это говорит о потенциальном повреждении файла/памяти, вместо этого будет инициализирован новый экземпляр.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?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} 缺少組建權杖而被停用</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} 已根據您的設定被停用。如果您想幫助 SteamDB 提交資料,請查看我們的 Wiki。</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},先感謝您的幫助。第一次提交將大約在 {1} 後進行。</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">
|
||||
<value>無法載入 {0},將初始化一個新實例…</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>此 Bot 中沒有需要重新整理的應用程式。</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>正在檢索共 {0} 個應用程式存取權杖…</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>正在檢索 {0} 個應用程式存取權杖…</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>已完成檢索 {0} 個應用程式存取權杖。</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>已完成檢索共 {0} 個應用程式存取權杖。</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>正在檢索共 {0} 個應用程式的 Depot…</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>正在檢索 {0} 個應用程式資料…</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>已完成檢索 {0} 個應用程式資料。</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>正在檢索 {0} 個應用程式的 Depot 金鑰…</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>已完成檢索 {0} 個應用程式的 Depot 金鑰。</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>已完成檢索共 {0} 個應用程式的 Depot 金鑰。</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>沒有要提交的新資料,一切都是最新狀態。</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>因為沒有可以讓我們歸類為貢獻者的有效 SteamID 集合,而無法提交資料。考慮設定 {0} 的屬性。</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>正在提交註冊的應用程式/程式包/Depot 共:{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>由於發送的請求過多造成提交失敗,我們將在約 {0} 後重試。</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>已成功提交資料。伺服器共已註冊了新的應用程式/程式包/Depot 共:{0}({1} 個已驗證)/{2}({3} 個已驗證)/{4}({5} 個已驗證)。</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>新的應用程式:{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>已驗證的應用程式:{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>新的程式包:{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>已驗證的程式包:{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>新的 Depot:{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>已被驗證的 Depot:{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} ,外掛程式將無法解析其中任何一個:{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>正在載入 STD 全域快取…</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>正在驗證 STD 全域快取完整性…</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>無法驗證 STD 全域快取完整性。這表示可能有檔案/記憶體損壞,將初始化一個新實例。</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -24,10 +24,11 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
internal static class SharedInfo {
|
||||
internal const byte ApiVersion = 2;
|
||||
internal const byte AppInfosPerSingleRequest = byte.MaxValue;
|
||||
internal const byte HoursBetweenUploads = 24;
|
||||
internal const byte MaximumHoursBetweenRefresh = 8; // Per single bot account, makes sense to be 2 or 3 times less than MinimumHoursBetweenUploads
|
||||
internal const byte MaximumMinutesBeforeFirstUpload = 60; // Must be greater or equal to MinimumMinutesBeforeFirstUpload
|
||||
internal const byte MinimumHoursBetweenUploads = 24;
|
||||
internal const byte MinimumMinutesBeforeFirstUpload = 10; // Must be less or equal to MaximumMinutesBeforeFirstUpload
|
||||
internal const byte MinimumMinutesBetweenUploads = 5; // Rate limiting for the server
|
||||
internal const string ServerURL = "https://asf-token-dumper.xpaw.me";
|
||||
internal const string Token = "STEAM_TOKEN_DUMPER_TOKEN"; // This is filled automatically during our CI build with API key provided by xPaw for ASF project
|
||||
|
||||
|
||||
@@ -20,13 +20,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ArchiSteamFarm.IPC.Integration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
public sealed class SteamTokenDumperConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Enabled { get; internal set; }
|
||||
@@ -44,7 +42,7 @@ public sealed class SteamTokenDumperConfig {
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SkipAutoGrantPackages { get; private set; }
|
||||
public bool SkipAutoGrantPackages { get; private set; } = true;
|
||||
|
||||
[JsonConstructor]
|
||||
internal SteamTokenDumperConfig() { }
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Composition;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -30,10 +30,12 @@ using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json;
|
||||
@@ -43,7 +45,7 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
[Export(typeof(IPlugin))]
|
||||
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotSteamClient, ISteamPICSChanges {
|
||||
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges {
|
||||
[JsonProperty]
|
||||
internal static SteamTokenDumperConfig? Config { get; private set; }
|
||||
|
||||
@@ -53,6 +55,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
private static readonly Timer SubmissionTimer = new(SubmitData);
|
||||
|
||||
private static GlobalCache? GlobalCache;
|
||||
private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue;
|
||||
|
||||
[JsonProperty]
|
||||
public override string Name => nameof(SteamTokenDumperPlugin);
|
||||
@@ -138,12 +141,48 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
|
||||
// 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(startIn, TimeSpan.FromHours(SharedInfo.MinimumHoursBetweenUploads));
|
||||
SubmissionTimer.Change(startIn, TimeSpan.FromHours(SharedInfo.HoursBetweenUploads));
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.PluginInitializedAndEnabled, nameof(SteamTokenDumperPlugin), startIn.ToHumanReadable()));
|
||||
}
|
||||
|
||||
public Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (!Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if ((args == null) || (args.Length == 0)) {
|
||||
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)));
|
||||
}
|
||||
|
||||
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);
|
||||
default:
|
||||
return Task.FromResult((string?) null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnBotDestroy(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
@@ -303,7 +342,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
HashSet<uint> appIDsToRefresh = new();
|
||||
|
||||
foreach (uint packageID in packageIDs.Where(static packageID => !Config.SecretPackageIDs.Contains(packageID))) {
|
||||
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs) packageData) || (packageData.AppIDs == null)) {
|
||||
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out PackageData? packageData) || (packageData.AppIDs == null)) {
|
||||
// ASF might not have the package info for us at the moment, we'll retry later
|
||||
continue;
|
||||
}
|
||||
@@ -460,6 +499,10 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
if (LastUploadAt + TimeSpan.FromMinutes(SharedInfo.MinimumMinutesBetweenUploads) > DateTimeOffset.UtcNow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await SubmissionSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
return;
|
||||
}
|
||||
@@ -475,7 +518,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
return;
|
||||
}
|
||||
|
||||
ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).OrderByDescending(static bot => bot.OwnedPackageIDs.Count).FirstOrDefault()?.SteamID ?? 0;
|
||||
ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).MaxBy(static bot => bot.OwnedPackageIDs.Count)?.SteamID ?? 0;
|
||||
|
||||
if (contributorSteamID == 0) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionNoContributorSet, nameof(ASF.GlobalConfig.SteamOwnerID)));
|
||||
@@ -484,11 +527,11 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
}
|
||||
|
||||
Uri request = new($"{SharedInfo.ServerURL}/submit");
|
||||
RequestData requestData = new(contributorSteamID, appTokens, packageTokens, depotKeys);
|
||||
SubmitRequest data = new(contributorSteamID, appTokens, packageTokens, depotKeys);
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionInProgress, appTokens.Count, packageTokens.Count, depotKeys.Count));
|
||||
|
||||
ObjectResponse<ResponseData>? response = await ASF.WebBrowser.UrlPostToJsonObject<ResponseData, RequestData>(request, data: requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
|
||||
ObjectResponse<SubmitResponse>? response = await ASF.WebBrowser.UrlPostToJsonObject<SubmitResponse, SubmitRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
ASF.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.WarningFailed);
|
||||
@@ -496,30 +539,51 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
return;
|
||||
}
|
||||
|
||||
// We've communicated with the server and didn't timeout, regardless of the success, this was the last upload attempt
|
||||
LastUploadAt = DateTimeOffset.UtcNow;
|
||||
|
||||
if (response.StatusCode.IsClientErrorCode()) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.WarningFailedWithError, response.StatusCode));
|
||||
|
||||
switch (response.StatusCode) {
|
||||
case HttpStatusCode.Forbidden:
|
||||
// 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) {
|
||||
SubmissionTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
break;
|
||||
case HttpStatusCode.Conflict:
|
||||
// SteamDB told us to reset our cache
|
||||
GlobalCache.Reset(true);
|
||||
|
||||
break;
|
||||
#if NETFRAMEWORK
|
||||
if (response.StatusCode == (HttpStatusCode) 429) {
|
||||
case (HttpStatusCode) 429:
|
||||
#else
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests) {
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
#endif
|
||||
|
||||
// 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));
|
||||
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
|
||||
|
||||
// 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(startIn, TimeSpan.FromHours(SharedInfo.MinimumHoursBetweenUploads));
|
||||
}
|
||||
// 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(startIn, TimeSpan.FromHours(SharedInfo.HoursBetweenUploads));
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionFailedTooManyRequests, startIn.ToHumanReadable()));
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionFailedTooManyRequests, startIn.ToHumanReadable()));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.Content.Success) {
|
||||
if (response.Content is not { Success: true }) {
|
||||
ASF.ArchiLogger.LogGenericError(ArchiSteamFarm.Localization.Strings.WarningFailed);
|
||||
|
||||
return;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static ArchiSteamFarm.Core.Utilities;
|
||||
@@ -34,9 +35,28 @@ public sealed class Utilities {
|
||||
[TestMethod]
|
||||
public void ContextSpecificWordsWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("archisteamfarmpassword").IsWeak);
|
||||
|
||||
[TestMethod]
|
||||
public void EasyPasswordsHaveMeaningfulReason() {
|
||||
(bool isWeak, string? reason) = TestPasswordStrength("CorrectHorse");
|
||||
|
||||
Assert.IsTrue(isWeak);
|
||||
Assert.IsTrue(reason?.Contains("Capitalization doesn't help very much", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LongPassphraseIsNotWeak() => Assert.IsFalse(TestPasswordStrength("10chars<!>asdf").IsWeak);
|
||||
|
||||
[TestMethod]
|
||||
public void MemePasswordIsNotWeak() => Assert.IsFalse(TestPasswordStrength("correcthorsebatterystaple").IsWeak);
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatedPasswordsHaveMeaningfulReason() {
|
||||
(bool isWeak, string? reason) = TestPasswordStrength("abcabcabc");
|
||||
|
||||
Assert.IsTrue(isWeak);
|
||||
Assert.IsTrue(reason?.Contains("Avoid repeated words and characters", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepetitiveCharactersWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("testaaaatest").IsWeak);
|
||||
|
||||
@@ -48,5 +68,13 @@ public sealed class Utilities {
|
||||
|
||||
[TestMethod]
|
||||
public void ShortPassphraseIsWeak() => Assert.IsTrue(TestPasswordStrength("four").IsWeak);
|
||||
|
||||
[TestMethod]
|
||||
public void StraightRowsPasswordsHaveMeaningfulReason() {
|
||||
(bool isWeak, string? reason) = TestPasswordStrength("`1234567890-=");
|
||||
|
||||
Assert.IsTrue(isWeak);
|
||||
Assert.IsTrue(reason?.Contains("Straight rows of keys are easy to guess", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1724 // We don't care about the potential conflict, as ASF class name has a priority
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.6.30114.105
|
||||
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.CustomPlugin
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper", "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.csproj", "{324E42B1-3C5D-421D-A600-1BEEE7AF401A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.OfficialPlugins.ItemsMatcher", "ArchiSteamFarm.OfficialPlugins.ItemsMatcher\ArchiSteamFarm.OfficialPlugins.ItemsMatcher.csproj", "{A8BA3425-2EE4-4333-9905-8867EDFE327F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -53,5 +55,11 @@ Global
|
||||
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
|
||||
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.DebugFast|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.DebugFast|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8BA3425-2EE4-4333-9905-8867EDFE327F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,23 +25,22 @@
|
||||
<PackageReference Include="zxcvbn-core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net48'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net481'">
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" />
|
||||
<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" />
|
||||
<Reference Include="System.Net.Http" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Net.Http.dll" />
|
||||
<Reference Include="System.Security" HintPath="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Security.dll" />
|
||||
<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>
|
||||
@@ -64,7 +63,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\LICENSE-2.0.txt">
|
||||
<Content Include="..\LICENSE.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
@@ -79,4 +78,20 @@
|
||||
<Link>www\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="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>
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="Exists($([System.IO.Path]::Combine('overlay', 'variant-specific', $(ASFVariant))))">
|
||||
<Content Include="overlay/variant-specific/$(ASFVariant)/**/*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -26,8 +26,10 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
#if ASF_SIGNED_BUILD
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.Tests, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")]
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.ItemsMatcher, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")]
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")]
|
||||
#else
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.Tests")]
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.ItemsMatcher")]
|
||||
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper")]
|
||||
#endif
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
||||
|
||||
set {
|
||||
if (value == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(value));
|
||||
ASF.ArchiLogger.LogNullError(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
126
ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs
Normal file
126
ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
[PublicAPI]
|
||||
public int Count => BackingDictionary.Count;
|
||||
|
||||
[PublicAPI]
|
||||
public bool IsEmpty => BackingDictionary.IsEmpty;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary = new();
|
||||
|
||||
int ICollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => BackingDictionary.Keys;
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys => BackingDictionary.Keys;
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => BackingDictionary.Values;
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values => BackingDictionary.Values;
|
||||
|
||||
public TValue this[TKey key] {
|
||||
get => BackingDictionary[key];
|
||||
set {
|
||||
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingDictionary[key] = value;
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
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 Clear() {
|
||||
if (BackingDictionary.IsEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingDictionary.Clear();
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
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 IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => BackingDictionary.GetEnumerator();
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item) {
|
||||
ICollection<KeyValuePair<TKey, TValue>> collection = BackingDictionary;
|
||||
|
||||
if (!collection.Remove(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) {
|
||||
if (!BackingDictionary.TryRemove(key, out _)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => 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!);
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryAdd(TKey key, TValue value) {
|
||||
if (!BackingDictionary.TryAdd(key, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryGetValue(TKey key, out TValue? value) => BackingDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
@@ -31,7 +31,6 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
@@ -88,7 +87,7 @@ public static class ASF {
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsOwner(ulong steamID) {
|
||||
if (steamID == 0) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
@@ -96,7 +95,7 @@ public static class ASF {
|
||||
}
|
||||
|
||||
internal static string GetFilePath(EFileType fileType) {
|
||||
if (!Enum.IsDefined(typeof(EFileType), fileType)) {
|
||||
if (!Enum.IsDefined(fileType)) {
|
||||
throw new InvalidEnumArgumentException(nameof(fileType), (int) fileType, typeof(EFileType));
|
||||
}
|
||||
|
||||
@@ -263,7 +262,7 @@ public static class ASF {
|
||||
}
|
||||
|
||||
if (binaryAsset.DownloadURL == null) {
|
||||
ArchiLogger.LogNullError(nameof(binaryAsset.DownloadURL));
|
||||
ArchiLogger.LogNullError(binaryAsset.DownloadURL);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -301,7 +300,7 @@ public static class ASF {
|
||||
progressReporter.ProgressChanged -= OnProgressChanged;
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
if (response?.Content == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -319,12 +318,16 @@ public static class ASF {
|
||||
|
||||
await PluginsCore.OnUpdateProceeding(newVersion).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
bool kestrelWasRunning = ArchiKestrel.IsRunning;
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We disable ArchiKestrel here as the update process moves the core files and might result in IPC crash
|
||||
// TODO: It might fail if the update was triggered from the API, this should be something to improve in the future, by changing the structure into request -> return response -> finish update
|
||||
await ArchiKestrel.Stop().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
try {
|
||||
await ArchiKestrel.Stop().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.PatchingFiles);
|
||||
@@ -342,6 +345,16 @@ public static class ASF {
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We've temporarily disabled ArchiKestrel but the update has failed, let's bring it back up
|
||||
// We can't even be sure if it's possible to bring it back up in this state, but it's worth trying anyway
|
||||
try {
|
||||
await ArchiKestrel.Start().ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
ArchiLogger.LogGenericWarningException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -404,34 +417,18 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async Task InitRateLimiters() {
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
// The only purpose of using hashing here is to cut on a potential size of the resource name - paths can be really long, and we almost certainly have some upper limit on the resource name we can allocate
|
||||
// At the same time it'd be the best if we avoided all special characters, such as '/' found e.g. in base64, as we can't be sure that it's not a prohibited character in regards to native OS implementation
|
||||
// Because of that, SHA256 is sufficient for our case, as it generates alphanumeric characters only, and is barely 256-bit long. We don't need any kind of complex cryptography or collision detection here, any hashing will do, and the shorter the better
|
||||
string networkGroupText = "";
|
||||
|
||||
if (!string.IsNullOrEmpty(Program.NetworkGroup)) {
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
networkGroupText = $"-{Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(Program.NetworkGroup!)))}";
|
||||
} else if (!string.IsNullOrEmpty(GlobalConfig.WebProxyText)) {
|
||||
networkGroupText = $"-{Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(GlobalConfig.WebProxyText!)))}";
|
||||
}
|
||||
|
||||
ConfirmationsSemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(ConfirmationsSemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
GiftsSemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(GiftsSemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
InventorySemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(InventorySemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
LoginRateLimitingSemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(LoginRateLimitingSemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
LoginSemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(LoginSemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
RateLimitingSemaphore ??= await PluginsCore.GetCrossProcessSemaphore($"{nameof(RateLimitingSemaphore)}{networkGroupText}").ConfigureAwait(false);
|
||||
ConfirmationsSemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(ConfirmationsSemaphore)).ConfigureAwait(false);
|
||||
GiftsSemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(GiftsSemaphore)).ConfigureAwait(false);
|
||||
InventorySemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(InventorySemaphore)).ConfigureAwait(false);
|
||||
LoginRateLimitingSemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(LoginRateLimitingSemaphore)).ConfigureAwait(false);
|
||||
LoginSemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(LoginSemaphore)).ConfigureAwait(false);
|
||||
RateLimitingSemaphore ??= await PluginsCore.GetCrossProcessSemaphore(nameof(RateLimitingSemaphore)).ConfigureAwait(false);
|
||||
|
||||
WebLimitingSemaphores ??= new Dictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>(4) {
|
||||
{ ArchiWebHandler.SteamCommunityURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}{networkGroupText}-{nameof(ArchiWebHandler.SteamCommunityURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamHelpURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}{networkGroupText}-{nameof(ArchiWebHandler.SteamHelpURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamStoreURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}{networkGroupText}-{nameof(ArchiWebHandler.SteamStoreURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ WebAPI.DefaultBaseAddress, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}{networkGroupText}-{nameof(WebAPI)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||
{ ArchiWebHandler.SteamCommunityURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamCommunityURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamHelpURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamHelpURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamStoreURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamStoreURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ WebAPI.DefaultBaseAddress, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(WebAPI)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||
}.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
@@ -461,9 +458,7 @@ public static class ASF {
|
||||
|
||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
|
||||
private static void LoadAssembliesRecursively(Assembly assembly, ISet<string> loadedAssembliesNames) {
|
||||
if (assembly == null) {
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(assembly);
|
||||
|
||||
if ((loadedAssembliesNames == null) || (loadedAssembliesNames.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(loadedAssembliesNames));
|
||||
@@ -824,32 +819,22 @@ public static class ASF {
|
||||
}
|
||||
|
||||
private static async void OnRenamed(object sender, RenamedEventArgs e) {
|
||||
// This function can be called with a possibility of OldName or (new) Name being null, we have to take it into account
|
||||
ArgumentNullException.ThrowIfNull(sender);
|
||||
ArgumentNullException.ThrowIfNull(e);
|
||||
|
||||
if (string.IsNullOrEmpty(e.OldName)) {
|
||||
throw new InvalidOperationException(nameof(e.OldName));
|
||||
if (!string.IsNullOrEmpty(e.OldName) && !string.IsNullOrEmpty(e.OldFullPath)) {
|
||||
await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.OldFullPath)) {
|
||||
throw new InvalidOperationException(nameof(e.OldFullPath));
|
||||
if (!string.IsNullOrEmpty(e.Name) && !string.IsNullOrEmpty(e.FullPath)) {
|
||||
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Name)) {
|
||||
throw new InvalidOperationException(nameof(e.Name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.FullPath)) {
|
||||
throw new InvalidOperationException(nameof(e.FullPath));
|
||||
}
|
||||
|
||||
await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false);
|
||||
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task RegisterBots() {
|
||||
if ((GlobalConfig == null) || (GlobalDatabase == null) || (WebBrowser == null)) {
|
||||
throw new ArgumentNullException($"{nameof(GlobalConfig)} || {nameof(GlobalDatabase)} || {nameof(WebBrowser)}");
|
||||
throw new InvalidOperationException($"{nameof(GlobalConfig)} || {nameof(GlobalDatabase)} || {nameof(WebBrowser)}");
|
||||
}
|
||||
|
||||
// Ensure that we ask for a list of servers if we don't have any saved servers available
|
||||
@@ -896,7 +881,7 @@ public static class ASF {
|
||||
|
||||
private static async Task UpdateAndRestart() {
|
||||
if (GlobalConfig == null) {
|
||||
throw new ArgumentNullException(nameof(GlobalConfig));
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (!SharedInfo.BuildInfo.CanUpdate || (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None)) {
|
||||
@@ -959,7 +944,7 @@ public static class ASF {
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(fileName)) {
|
||||
ArchiLogger.LogNullError(nameof(fileName));
|
||||
ArchiLogger.LogNullError(fileName);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -967,7 +952,7 @@ public static class ASF {
|
||||
string relativeFilePath = Path.GetRelativePath(targetDirectory, file);
|
||||
|
||||
if (string.IsNullOrEmpty(relativeFilePath)) {
|
||||
ArchiLogger.LogNullError(nameof(relativeFilePath));
|
||||
ArchiLogger.LogNullError(relativeFilePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -976,7 +961,7 @@ public static class ASF {
|
||||
|
||||
switch (relativeDirectoryName) {
|
||||
case null:
|
||||
ArchiLogger.LogNullError(nameof(relativeDirectoryName));
|
||||
ArchiLogger.LogNullError(relativeDirectoryName);
|
||||
|
||||
return false;
|
||||
case "":
|
||||
@@ -1040,7 +1025,7 @@ public static class ASF {
|
||||
string? directory = Path.GetDirectoryName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
ArchiLogger.LogNullError(nameof(directory));
|
||||
ArchiLogger.LogNullError(directory);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1070,7 +1055,8 @@ public static class ASF {
|
||||
Password,
|
||||
SteamGuard,
|
||||
SteamParentalCode,
|
||||
TwoFactorAuthentication
|
||||
TwoFactorAuthentication,
|
||||
Cryptkey
|
||||
}
|
||||
|
||||
internal enum EFileType : byte {
|
||||
|
||||
@@ -36,7 +36,7 @@ internal static class AprilFools {
|
||||
internal static void Init(object? state = null) {
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
if ((now.Month == 4) && (now.Day == 1)) {
|
||||
if (now is { Month: 4, Day: 1 }) {
|
||||
try {
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(SharedInfo.LolcatCultureName);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -20,68 +20,22 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal static class ArchiNet {
|
||||
private static Uri URL => new("https://asf.JustArchi.net");
|
||||
internal static Uri URL => new("https://asf.JustArchi.net");
|
||||
|
||||
internal static async Task<HttpStatusCode?> AnnounceForListing(Bot bot, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tradeToken)) {
|
||||
throw new ArgumentNullException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
if (tradeToken.Length != BotConfig.SteamTradeTokenLength) {
|
||||
throw new ArgumentOutOfRangeException(nameof(tradeToken));
|
||||
}
|
||||
|
||||
Uri request = new(URL, "/Api/Announce");
|
||||
|
||||
Dictionary<string, string> data = new(9, StringComparer.Ordinal) {
|
||||
{ "AvatarHash", avatarHash ?? "" },
|
||||
{ "GamesCount", inventory.Select(static item => item.RealAppID).Distinct().Count().ToString(CultureInfo.InvariantCulture) },
|
||||
{ "Guid", (ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid()).ToString("N") },
|
||||
{ "ItemsCount", inventory.Count.ToString(CultureInfo.InvariantCulture) },
|
||||
{ "MatchableTypes", JsonConvert.SerializeObject(acceptedMatchableTypes) },
|
||||
{ "MatchEverything", bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) ? "1" : "0" },
|
||||
{ "Nickname", nickname ?? "" },
|
||||
{ "SteamID", bot.SteamID.ToString(CultureInfo.InvariantCulture) },
|
||||
{ "TradeToken", tradeToken }
|
||||
};
|
||||
|
||||
BasicResponse? response = await bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
|
||||
|
||||
return response?.StatusCode;
|
||||
}
|
||||
private static readonly ArchiCacheable<ImmutableHashSet<ulong>> CachedBadBots = new(ResolveCachedBadBots, TimeSpan.FromDays(1));
|
||||
|
||||
internal static async Task<string?> FetchBuildChecksum(Version version, string variant) {
|
||||
if (version == null) {
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(version);
|
||||
|
||||
if (string.IsNullOrEmpty(variant)) {
|
||||
throw new ArgumentNullException(nameof(variant));
|
||||
@@ -93,178 +47,34 @@ internal static class ArchiNet {
|
||||
|
||||
Uri request = new(URL, $"/Api/Checksum/{version}/{variant}");
|
||||
|
||||
ObjectResponse<ChecksumResponse>? response = await ASF.WebBrowser.UrlGetToJsonObject<ChecksumResponse>(request).ConfigureAwait(false);
|
||||
ObjectResponse<GenericResponse<string>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<string>>(request).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
if (response?.Content == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.Content.Checksum ?? "";
|
||||
return response.Content.Result ?? "";
|
||||
}
|
||||
|
||||
internal static async Task<ImmutableHashSet<ListedUser>?> GetListedUsers(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
internal static async Task<bool?> IsBadBot(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
Uri request = new(URL, "/Api/Bots");
|
||||
(_, ImmutableHashSet<ulong>? badBots) = await CachedBadBots.GetValue(ArchiCacheable<ImmutableHashSet<ulong>>.EFallback.SuccessPreviously).ConfigureAwait(false);
|
||||
|
||||
ObjectResponse<ImmutableHashSet<ListedUser>>? response = await bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject<ImmutableHashSet<ListedUser>>(request).ConfigureAwait(false);
|
||||
|
||||
return response?.Content;
|
||||
return badBots?.Contains(steamID);
|
||||
}
|
||||
|
||||
internal static async Task<HttpStatusCode?> HeartBeatForListing(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
Uri request = new(URL, "/Api/HeartBeat");
|
||||
|
||||
Dictionary<string, string> data = new(2, StringComparer.Ordinal) {
|
||||
{ "Guid", (ASF.GlobalDatabase?.Identifier ?? Guid.NewGuid()).ToString("N") },
|
||||
{ "SteamID", bot.SteamID.ToString(CultureInfo.InvariantCulture) }
|
||||
};
|
||||
|
||||
BasicResponse? response = await bot.ArchiWebHandler.WebBrowser.UrlPost(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
|
||||
|
||||
return response?.StatusCode;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class ListedUser {
|
||||
#pragma warning disable CS0649 // False positive, it's a field set during json deserialization
|
||||
[JsonProperty(PropertyName = "items_count", Required = Required.Always)]
|
||||
internal readonly ushort ItemsCount;
|
||||
#pragma warning restore CS0649 // False positive, it's a field set during json deserialization
|
||||
|
||||
internal readonly HashSet<Asset.EType> MatchableTypes = new();
|
||||
|
||||
#pragma warning disable CS0649 // False positive, it's a field set during json deserialization
|
||||
[JsonProperty(PropertyName = "steam_id", Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
#pragma warning restore CS0649 // False positive, it's a field set during json deserialization
|
||||
|
||||
[JsonProperty(PropertyName = "trade_token", Required = Required.Always)]
|
||||
internal readonly string TradeToken = "";
|
||||
|
||||
internal float Score => GamesCount / (float) ItemsCount;
|
||||
|
||||
#pragma warning disable CS0649 // False positive, it's a field set during json deserialization
|
||||
[JsonProperty(PropertyName = "games_count", Required = Required.Always)]
|
||||
private readonly ushort GamesCount;
|
||||
#pragma warning restore CS0649 // False positive, it's a field set during json deserialization
|
||||
|
||||
internal bool MatchEverything { get; private set; }
|
||||
|
||||
[JsonProperty(PropertyName = "matchable_backgrounds", Required = Required.Always)]
|
||||
private byte MatchableBackgroundsNumber {
|
||||
set {
|
||||
switch (value) {
|
||||
case 0:
|
||||
MatchableTypes.Remove(Asset.EType.ProfileBackground);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
MatchableTypes.Add(Asset.EType.ProfileBackground);
|
||||
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(value), value));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
private static async Task<(bool Success, ImmutableHashSet<ulong>? Result)> ResolveCachedBadBots() {
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "matchable_cards", Required = Required.Always)]
|
||||
private byte MatchableCardsNumber {
|
||||
set {
|
||||
switch (value) {
|
||||
case 0:
|
||||
MatchableTypes.Remove(Asset.EType.TradingCard);
|
||||
Uri request = new(URL, "/Api/BadBots");
|
||||
|
||||
break;
|
||||
case 1:
|
||||
MatchableTypes.Add(Asset.EType.TradingCard);
|
||||
ObjectResponse<GenericResponse<ImmutableHashSet<ulong>>>? response = await ASF.WebBrowser.UrlGetToJsonObject<GenericResponse<ImmutableHashSet<ulong>>>(request).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(value), value));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "matchable_emoticons", Required = Required.Always)]
|
||||
private byte MatchableEmoticonsNumber {
|
||||
set {
|
||||
switch (value) {
|
||||
case 0:
|
||||
MatchableTypes.Remove(Asset.EType.Emoticon);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
MatchableTypes.Add(Asset.EType.Emoticon);
|
||||
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(value), value));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "matchable_foil_cards", Required = Required.Always)]
|
||||
private byte MatchableFoilCardsNumber {
|
||||
set {
|
||||
switch (value) {
|
||||
case 0:
|
||||
MatchableTypes.Remove(Asset.EType.FoilTradingCard);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
MatchableTypes.Add(Asset.EType.FoilTradingCard);
|
||||
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(value), value));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "match_everything", Required = Required.Always)]
|
||||
private byte MatchEverythingNumber {
|
||||
set {
|
||||
switch (value) {
|
||||
case 0:
|
||||
MatchEverything = false;
|
||||
|
||||
break;
|
||||
case 1:
|
||||
MatchEverything = true;
|
||||
|
||||
break;
|
||||
default:
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(value), value));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private ListedUser() { }
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
private sealed class ChecksumResponse {
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("checksum", Required = Required.AllowNull)]
|
||||
internal readonly string? Checksum;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
|
||||
[JsonConstructor]
|
||||
private ChecksumResponse() { }
|
||||
return response?.Content != null ? (true, response.Content.Result) : (false, null);
|
||||
}
|
||||
}
|
||||
|
||||
51
ArchiSteamFarm/Core/GeneratedRegexes.cs
Normal file
51
ArchiSteamFarm/Core/GeneratedRegexes.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Text.RegularExpressions;
|
||||
|
||||
namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal static partial class GeneratedRegexes {
|
||||
private const string CdKeyPattern = @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$";
|
||||
private const string DecimalPattern = @"[0-9\.]+";
|
||||
private const RegexOptions DefaultOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
||||
private const string DigitsPattern = @"\d+";
|
||||
private const string NonAsciiPattern = @"[^\u0000-\u007F]+";
|
||||
|
||||
#if NETFRAMEWORK
|
||||
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();
|
||||
|
||||
[GeneratedRegex(DecimalPattern, DefaultOptions)]
|
||||
internal static partial Regex Decimal();
|
||||
|
||||
[GeneratedRegex(DigitsPattern, DefaultOptions)]
|
||||
internal static partial Regex Digits();
|
||||
|
||||
[GeneratedRegex(NonAsciiPattern, DefaultOptions)]
|
||||
internal static partial Regex NonAscii();
|
||||
#endif
|
||||
}
|
||||
114
ArchiSteamFarm/Core/NativeMethods.cs
Normal file
114
ArchiSteamFarm/Core/NativeMethods.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal static partial class NativeMethods {
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const EExecutionState AwakeExecutionState = EExecutionState.SystemRequired | EExecutionState.AwayModeRequired | EExecutionState.Continuous;
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const uint EnableQuickEditMode = 0x0040;
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const sbyte StandardInputHandle = -10;
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
#if NETFRAMEWORK
|
||||
#pragma warning disable CA2101 // False positive, we can't use unicode charset on Unix, and it uses UTF-8 by default anyway
|
||||
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
|
||||
internal static extern int Chmod(string path, int mode);
|
||||
#pragma warning restore CA2101 // False positive, we can't use unicode charset on Unix, and it uses UTF-8 by default anyway
|
||||
#else
|
||||
[LibraryImport("libc", EntryPoint = "chmod", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)]
|
||||
internal static partial int Chmod(string path, int mode);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
#if NETFRAMEWORK
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool GetConsoleMode(nint hConsoleHandle, out uint lpMode);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial bool GetConsoleMode(nint hConsoleHandle, out uint lpMode);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
#if NETFRAMEWORK
|
||||
[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
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern nint GetStdHandle(int nStdHandle);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial nint GetStdHandle(int nStdHandle);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
#if NETFRAMEWORK
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
|
||||
#endif
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
#if NETFRAMEWORK
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
#else
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
#endif
|
||||
|
||||
[Flags]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal enum EExecutionState : uint {
|
||||
None = 0,
|
||||
SystemRequired = 0x00000001,
|
||||
AwayModeRequired = 0x00000040,
|
||||
Continuous = 0x80000000
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -121,8 +122,8 @@ internal static class OS {
|
||||
}
|
||||
|
||||
internal static void Init(GlobalConfig.EOptimizationMode optimizationMode) {
|
||||
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
|
||||
throw new ArgumentNullException(nameof(optimizationMode));
|
||||
if (!Enum.IsDefined(optimizationMode)) {
|
||||
throw new InvalidEnumArgumentException(nameof(optimizationMode), (int) optimizationMode, typeof(GlobalConfig.EOptimizationMode));
|
||||
}
|
||||
|
||||
switch (optimizationMode) {
|
||||
@@ -147,7 +148,7 @@ internal static class OS {
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) {
|
||||
return NativeMethods.GetEUID() == 0;
|
||||
return NativeMethods.GetEuid() == 0;
|
||||
}
|
||||
|
||||
// We can't determine whether user is running as root or not, so fallback to that not happening
|
||||
@@ -226,34 +227,67 @@ internal static class OS {
|
||||
}
|
||||
|
||||
internal static bool VerifyEnvironment() {
|
||||
#if NETFRAMEWORK
|
||||
// This is .NET Framework build, we support that one only on mono for platforms not supported by .NET Core
|
||||
|
||||
// We're not going to analyze source builds, as we don't know what changes the author has made, assume they have a point
|
||||
if (SharedInfo.BuildInfo.IsCustomBuild) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// All windows variants have valid .NET Core build, and generic-netf is supported only on mono
|
||||
if (OperatingSystem.IsWindows() || !RuntimeMadness.IsRunningOnMono) {
|
||||
return false;
|
||||
}
|
||||
if (SharedInfo.BuildInfo.Variant.EndsWith("-netf", StringComparison.Ordinal)) {
|
||||
#if NETFRAMEWORK
|
||||
// All Windows variants (7+) have valid .NET Core build
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
// Non-Windows variants of generic-netf are supported only in Mono
|
||||
if (!RuntimeMadness.IsRunningOnMono) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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,
|
||||
|
||||
// Everything else is covered by .NET Core
|
||||
_ => false
|
||||
};
|
||||
// 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
|
||||
|
||||
// This is .NET Core build, we support all scenarios
|
||||
return true;
|
||||
// .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;
|
||||
}
|
||||
|
||||
if ((SharedInfo.BuildInfo.Variant == "docker") || SharedInfo.BuildInfo.Variant.StartsWith("linux-", StringComparison.Ordinal)) {
|
||||
// OS-specific Linux and Docker builds are supported only on Linux
|
||||
return OperatingSystem.IsLinux();
|
||||
}
|
||||
|
||||
if (SharedInfo.BuildInfo.Variant.StartsWith("osx-", StringComparison.Ordinal)) {
|
||||
// OS-specific macOS build is supported only on macOS
|
||||
return OperatingSystem.IsMacOS();
|
||||
}
|
||||
|
||||
if (SharedInfo.BuildInfo.Variant.StartsWith("win-", StringComparison.Ordinal)) {
|
||||
// OS-specific Windows build is supported only on Windows
|
||||
return OperatingSystem.IsWindows();
|
||||
}
|
||||
|
||||
// Unknown combination, we intend to cover all of the available ones above, so this results in an error
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(SharedInfo.BuildInfo.Variant), SharedInfo.BuildInfo.Variant));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
@@ -262,7 +296,7 @@ internal static class OS {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
|
||||
nint consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
|
||||
|
||||
if (!NativeMethods.GetConsoleMode(consoleHandle, out uint consoleMode)) {
|
||||
ASF.ArchiLogger.LogGenericError(Strings.WarningFailed);
|
||||
@@ -310,60 +344,4 @@ internal static class OS {
|
||||
UserRead = 0x100,
|
||||
Combined777 = UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute
|
||||
}
|
||||
|
||||
private static class NativeMethods {
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const EExecutionState AwakeExecutionState = EExecutionState.SystemRequired | EExecutionState.AwayModeRequired | EExecutionState.Continuous;
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const uint EnableQuickEditMode = 0x0040;
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal const sbyte StandardInputHandle = -10;
|
||||
|
||||
#pragma warning disable CA2101 // False positive, we can't use unicode charset on Unix, and it uses UTF-8 by default anyway
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
internal static extern int Chmod(string path, int mode);
|
||||
#pragma warning restore CA2101 // False positive, we can't use unicode charset on Unix, and it uses UTF-8 by default anyway
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("libc", EntryPoint = "geteuid", SetLastError = true)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
internal static extern uint GetEUID();
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[DllImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static extern EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
|
||||
[Flags]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal enum EExecutionState : uint {
|
||||
None = 0,
|
||||
SystemRequired = 0x00000001,
|
||||
AwayModeRequired = 0x00000040,
|
||||
Continuous = 0x80000000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Resources;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
@@ -78,10 +77,7 @@ public static class Utilities {
|
||||
[PublicAPI]
|
||||
public static string? GetCookieValue(this CookieContainer cookieContainer, Uri uri, string name) {
|
||||
ArgumentNullException.ThrowIfNull(cookieContainer);
|
||||
|
||||
if (uri == null) {
|
||||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(uri);
|
||||
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
@@ -97,7 +93,7 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static uint GetUnixTime() => (uint) DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
public static ulong GetUnixTime() => (ulong) DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
[PublicAPI]
|
||||
public static async void InBackground(Action action, bool longRunning = false) {
|
||||
@@ -113,16 +109,10 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static async void InBackground<T>(Func<T> function, bool longRunning = false) {
|
||||
public static void InBackground<T>(Func<T> function, bool longRunning = false) {
|
||||
ArgumentNullException.ThrowIfNull(function);
|
||||
|
||||
TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
|
||||
|
||||
if (longRunning) {
|
||||
options |= TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness;
|
||||
}
|
||||
|
||||
await Task.Factory.StartNew(function, CancellationToken.None, options, TaskScheduler.Default).ConfigureAwait(false);
|
||||
InBackground(void() => function(), longRunning);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -170,6 +160,9 @@ public static class Utilities {
|
||||
[PublicAPI]
|
||||
public static bool IsClientErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError;
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsRedirectionCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.Ambiguous and < HttpStatusCode.BadRequest;
|
||||
|
||||
[PublicAPI]
|
||||
public static bool IsServerErrorCode(this HttpStatusCode statusCode) => statusCode is >= HttpStatusCode.InternalServerError and < (HttpStatusCode) 600;
|
||||
|
||||
@@ -182,7 +175,7 @@ public static class Utilities {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
return GeneratedRegexes.CdKey().IsMatch(key);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -195,20 +188,45 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static IEnumerable<IElement> SelectNodes(this IDocument document, string xpath) {
|
||||
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
return document.Body.SelectNodes(xpath).OfType<IElement>();
|
||||
return document.Body.SelectNodes(xpath);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static IElement? SelectSingleElementNode(this IElement element, string xpath) => (IElement?) element.SelectSingleNode(xpath);
|
||||
|
||||
[PublicAPI]
|
||||
public static IElement? SelectSingleNode(this IDocument document, string xpath) {
|
||||
public static IEnumerable<T> SelectNodes<T>(this IDocument document, string xpath) where T : class, INode {
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
return (IElement?) document.Body.SelectSingleNode(xpath);
|
||||
return document.Body.SelectNodes(xpath).OfType<T>();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static IEnumerable<T> SelectNodes<T>(this IElement element, string xpath) where T : class, INode {
|
||||
ArgumentNullException.ThrowIfNull(element);
|
||||
|
||||
return element.SelectNodes(xpath).OfType<T>();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static INode? SelectSingleNode(this IDocument document, string xpath) {
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
return document.Body.SelectSingleNode(xpath);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static T? SelectSingleNode<T>(this IDocument document, string xpath) where T : class, INode {
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
return document.Body.SelectSingleNode(xpath) as T;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static T? SelectSingleNode<T>(this IElement element, string xpath) where T : class, INode {
|
||||
ArgumentNullException.ThrowIfNull(element);
|
||||
|
||||
return element.SelectSingleNode(xpath) as T;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -259,6 +277,14 @@ public static class Utilities {
|
||||
}
|
||||
}
|
||||
|
||||
internal static ulong MathAdd(ulong first, int second) {
|
||||
if (second >= 0) {
|
||||
return first + (uint) second;
|
||||
}
|
||||
|
||||
return first - (uint) -second;
|
||||
}
|
||||
|
||||
internal static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) {
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
throw new ArgumentNullException(nameof(directory));
|
||||
@@ -285,9 +311,28 @@ public static class Utilities {
|
||||
}
|
||||
|
||||
Result result = Zxcvbn.Core.EvaluatePassword(password, forbiddenPhrases);
|
||||
FeedbackItem feedback = result.Feedback;
|
||||
|
||||
return (result.Score < 4, string.IsNullOrEmpty(feedback.Warning) ? feedback.Suggestions.FirstOrDefault() : feedback.Warning);
|
||||
IList<string>? suggestions = result.Feedback.Suggestions;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Feedback.Warning)) {
|
||||
suggestions ??= new List<string>(1);
|
||||
|
||||
suggestions.Insert(0, result.Feedback.Warning);
|
||||
}
|
||||
|
||||
if (suggestions != null) {
|
||||
for (byte i = 0; i < suggestions.Count; i++) {
|
||||
string suggestion = suggestions[i];
|
||||
|
||||
if ((suggestion.Length == 0) || (suggestion[^1] == '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestions[i] = $"{suggestion}.";
|
||||
}
|
||||
}
|
||||
|
||||
return (result.Score < 4, suggestions is { Count: > 0 } ? string.Join(" ", suggestions.Where(static suggestion => suggestion.Length > 0)) : null);
|
||||
}
|
||||
|
||||
internal static void WarnAboutIncompleteTranslation(ResourceManager resourceManager) {
|
||||
@@ -295,9 +340,7 @@ public static class Utilities {
|
||||
|
||||
// Skip translation progress for English and invariant (such as "C") cultures
|
||||
switch (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName) {
|
||||
case "en":
|
||||
case "iv":
|
||||
case "qps":
|
||||
case "en" or "iv" or "qps":
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -305,7 +348,7 @@ public static class Utilities {
|
||||
ResourceSet? defaultResourceSet = resourceManager.GetResourceSet(CultureInfo.GetCultureInfo("en-US"), true, true);
|
||||
|
||||
if (defaultResourceSet == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(defaultResourceSet));
|
||||
ASF.ArchiLogger.LogNullError(defaultResourceSet);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -313,7 +356,7 @@ public static class Utilities {
|
||||
HashSet<DictionaryEntry> defaultStringObjects = defaultResourceSet.Cast<DictionaryEntry>().ToHashSet();
|
||||
|
||||
if (defaultStringObjects.Count == 0) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(defaultStringObjects));
|
||||
ASF.ArchiLogger.LogNullError(defaultStringObjects);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -322,7 +365,7 @@ public static class Utilities {
|
||||
ResourceSet? currentResourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
|
||||
|
||||
if (currentResourceSet == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(currentResourceSet));
|
||||
ASF.ArchiLogger.LogNullError(currentResourceSet);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<(bool Success, T? Result)> GetValue(EFallback fallback = EFallback.DefaultForType) {
|
||||
if (!Enum.IsDefined(typeof(EFallback), fallback)) {
|
||||
if (!Enum.IsDefined(fallback)) {
|
||||
throw new InvalidEnumArgumentException(nameof(fallback), (int) fallback, typeof(EFallback));
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public static class ArchiCryptoHelper {
|
||||
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
||||
|
||||
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string encryptedString) {
|
||||
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod)) {
|
||||
if (!Enum.IsDefined(cryptoMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public static class ArchiCryptoHelper {
|
||||
}
|
||||
|
||||
internal static string? Encrypt(ECryptoMethod cryptoMethod, string decryptedString) {
|
||||
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod)) {
|
||||
if (!Enum.IsDefined(cryptoMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public static class ArchiCryptoHelper {
|
||||
}
|
||||
|
||||
internal static string Hash(EHashingMethod hashingMethod, string stringToHash) {
|
||||
if (!Enum.IsDefined(typeof(EHashingMethod), hashingMethod)) {
|
||||
if (!Enum.IsDefined(hashingMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public static class ArchiCryptoHelper {
|
||||
throw new ArgumentOutOfRangeException(nameof(hashLength));
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(typeof(EHashingMethod), hashingMethod)) {
|
||||
if (!Enum.IsDefined(hashingMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||
}
|
||||
|
||||
@@ -159,11 +159,15 @@ public static class ArchiCryptoHelper {
|
||||
throw new ArgumentNullException(nameof(passwordHash));
|
||||
}
|
||||
|
||||
if (passwordHash.Length > byte.MaxValue) {
|
||||
throw new ArgumentOutOfRangeException(nameof(passwordHash));
|
||||
}
|
||||
|
||||
if ((salt == null) || (salt.Length == 0)) {
|
||||
throw new ArgumentNullException(nameof(salt));
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(typeof(EHashingMethod), hashingMethod)) {
|
||||
if (!Enum.IsDefined(hashingMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ using ArchiSteamFarm.Core;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers;
|
||||
|
||||
internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, IDisposable {
|
||||
internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossProcessSemaphore, IDisposable {
|
||||
private const ushort SpinLockDelay = 1000; // In milliseconds
|
||||
|
||||
private readonly string FilePath;
|
||||
@@ -48,11 +48,23 @@ internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, I
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
LocalSemaphore.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
FileLock?.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
LocalSemaphore.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
if (FileLock != null) {
|
||||
await FileLock.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ICrossProcessSemaphore.Release() {
|
||||
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
||||
lock (LocalSemaphore) {
|
||||
@@ -113,6 +125,10 @@ internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, I
|
||||
try {
|
||||
stopwatch.Stop();
|
||||
|
||||
if (stopwatch.ElapsedMilliseconds >= int.MaxValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
|
||||
|
||||
if (millisecondsTimeout <= 0) {
|
||||
@@ -158,7 +174,7 @@ internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore, I
|
||||
string? directoryPath = Path.GetDirectoryName(FilePath);
|
||||
|
||||
if (string.IsNullOrEmpty(directoryPath)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(directoryPath));
|
||||
ASF.ArchiLogger.LogNullError(directoryPath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ using NLog.Web;
|
||||
namespace ArchiSteamFarm.IPC;
|
||||
|
||||
internal static class ArchiKestrel {
|
||||
internal static bool IsRunning => KestrelWebHost != null;
|
||||
|
||||
internal static HistoryTarget? HistoryTarget { get; private set; }
|
||||
|
||||
private static IHost? KestrelWebHost;
|
||||
@@ -74,7 +76,27 @@ internal static class ArchiKestrel {
|
||||
// Set default content root
|
||||
builder.UseContentRoot(SharedInfo.HomeDirectory);
|
||||
|
||||
// Firstly initialize settings that user is free to override
|
||||
// Check if custom config is available
|
||||
string absoluteConfigDirectory = Path.Combine(Directory.GetCurrentDirectory(), SharedInfo.ConfigDirectory);
|
||||
string customConfigPath = Path.Combine(absoluteConfigDirectory, SharedInfo.IPCConfigFile);
|
||||
|
||||
bool customConfigExists = File.Exists(customConfigPath);
|
||||
|
||||
if (customConfigExists && Debugging.IsDebugConfigured) {
|
||||
try {
|
||||
string json = await File.ReadAllTextAsync(customConfigPath).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(json)) {
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
ASF.ArchiLogger.LogGenericDebug($"{SharedInfo.IPCConfigFile}: {jObject.ToString(Formatting.Indented)}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable NLog integration for logging
|
||||
builder.ConfigureLogging(
|
||||
static logging => {
|
||||
logging.ClearProviders();
|
||||
@@ -82,33 +104,7 @@ internal static class ArchiKestrel {
|
||||
}
|
||||
);
|
||||
|
||||
// Check if custom config is available
|
||||
string absoluteConfigDirectory = Path.Combine(Directory.GetCurrentDirectory(), SharedInfo.ConfigDirectory);
|
||||
string customConfigPath = Path.Combine(absoluteConfigDirectory, SharedInfo.IPCConfigFile);
|
||||
|
||||
bool customConfigExists = File.Exists(customConfigPath);
|
||||
|
||||
if (customConfigExists) {
|
||||
if (Debugging.IsDebugConfigured) {
|
||||
try {
|
||||
string json = await File.ReadAllTextAsync(customConfigPath).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(json)) {
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
ASF.ArchiLogger.LogGenericDebug($"{SharedInfo.IPCConfigFile}: {jObject.ToString(Formatting.Indented)}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom config for logging configuration
|
||||
builder.ConfigureLogging(static (hostingContext, logging) => logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")));
|
||||
}
|
||||
|
||||
// Enable NLog integration for logging
|
||||
builder.UseNLog();
|
||||
builder.UseNLog(new NLogAspNetCoreOptions { ShutdownOnDispose = false });
|
||||
|
||||
builder.ConfigureWebHostDefaults(
|
||||
webBuilder => {
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class ASFController : ArchiController {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts data with ASF encryption mechanisms using provided details.
|
||||
/// Hashes data with ASF hashing mechanisms using provided details.
|
||||
/// </summary>
|
||||
[Consumes("application/json")]
|
||||
[HttpPost("Hash")]
|
||||
|
||||
@@ -123,7 +123,10 @@ public sealed class BotController : ArchiController {
|
||||
}
|
||||
|
||||
if (!request.BotConfig.IsSteamPasswordSet && bot.BotConfig.IsSteamPasswordSet) {
|
||||
request.BotConfig.SetDecryptedSteamPassword(await bot.BotConfig.GetDecryptedSteamPassword().ConfigureAwait(false));
|
||||
request.BotConfig.SteamPassword = bot.BotConfig.SteamPassword;
|
||||
|
||||
// Since we're inheriting the password, we should also inherit the format, whatever that might be
|
||||
request.BotConfig.PasswordFormat = bot.BotConfig.PasswordFormat;
|
||||
}
|
||||
|
||||
if (!request.BotConfig.IsSteamParentalCodeSet && bot.BotConfig.IsSteamParentalCodeSet) {
|
||||
@@ -261,7 +264,7 @@ public sealed class BotController : ArchiController {
|
||||
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if ((request.Type == ASF.EUserInputType.None) || !Enum.IsDefined(typeof(ASF.EUserInputType), request.Type) || string.IsNullOrEmpty(request.Value)) {
|
||||
if ((request.Type == ASF.EUserInputType.None) || !Enum.IsDefined(request.Type) || string.IsNullOrEmpty(request.Value)) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, $"{nameof(request.Type)} || {nameof(request.Value)}")));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,12 +54,6 @@ public sealed class CommandController : ArchiController {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(request.Command))));
|
||||
}
|
||||
|
||||
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
|
||||
|
||||
if (steamOwnerID == 0) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(ASF.GlobalConfig.SteamOwnerID))));
|
||||
}
|
||||
|
||||
Bot? targetBot = Bot.Bots?.OrderBy(static bot => bot.Key, Bot.BotsComparer).Select(static bot => bot.Value).FirstOrDefault();
|
||||
|
||||
if (targetBot == null) {
|
||||
@@ -80,7 +74,7 @@ public sealed class CommandController : ArchiController {
|
||||
command = command[commandPrefix.Length..];
|
||||
}
|
||||
|
||||
string? response = await targetBot.Commands.Response(steamOwnerID, command).ConfigureAwait(false);
|
||||
string? response = await targetBot.Commands.Response(EAccess.Owner, command).ConfigureAwait(false);
|
||||
|
||||
return Ok(new GenericResponse<string>(response));
|
||||
}
|
||||
|
||||
77
ArchiSteamFarm/IPC/Controllers/Api/IPCController.cs
Normal file
77
ArchiSteamFarm/IPC/Controllers/Api/IPCController.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using ArchiSteamFarm.IPC.Integration;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
|
||||
[Route("Api/IPC")]
|
||||
public sealed class IPCController : ArchiController {
|
||||
/// <summary>
|
||||
/// Clears the list of all IP addresses currently blocked by ASFs IPC module
|
||||
/// </summary>
|
||||
[HttpDelete("Bans")]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)]
|
||||
public ActionResult<GenericResponse> BansDelete() {
|
||||
ApiAuthenticationMiddleware.ClearFailedAuthorizations();
|
||||
|
||||
return Ok(new GenericResponse(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an IP address from the list of addresses currently blocked by ASFs IPC module
|
||||
/// </summary>
|
||||
[HttpDelete("Bans/{ipAddress:required}")]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public ActionResult<GenericResponse> BansDeleteSpecific(string ipAddress) {
|
||||
if (string.IsNullOrEmpty(ipAddress)) {
|
||||
throw new ArgumentNullException(nameof(ipAddress));
|
||||
}
|
||||
|
||||
if (!IPAddress.TryParse(ipAddress, out IPAddress? remoteAddress)) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(ipAddress))));
|
||||
}
|
||||
|
||||
bool result = ApiAuthenticationMiddleware.UnbanIP(remoteAddress);
|
||||
|
||||
if (!result) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIPNotBanned, ipAddress)));
|
||||
}
|
||||
|
||||
return Ok(new GenericResponse(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all IP addresses currently blocked by ASFs IPC module
|
||||
/// </summary>
|
||||
[HttpGet("Bans")]
|
||||
[ProducesResponseType(typeof(GenericResponse<ISet<string>>), (int) HttpStatusCode.OK)]
|
||||
public ActionResult<GenericResponse<ISet<string>>> BansGet() => Ok(new GenericResponse<ISet<string>>(ApiAuthenticationMiddleware.GetCurrentlyBannedIPs().Select(static ip => ip.ToString()).ToHashSet()));
|
||||
}
|
||||
@@ -32,6 +32,7 @@ using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
using ArchiSteamFarm.NLog.Targets;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -43,6 +44,43 @@ namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
public sealed class NLogController : ArchiController {
|
||||
private static readonly ConcurrentDictionary<WebSocket, (SemaphoreSlim Semaphore, CancellationToken CancellationToken)> ActiveLogWebSockets = new();
|
||||
|
||||
/// <summary>
|
||||
/// Fetches ASF log file, this works on assumption that the log file is in fact generated, as user could disable it through custom configuration.
|
||||
/// </summary>
|
||||
/// <param name="count">Maximum amount of lines from the log file returned. The respone naturally might have less amount than specified, if you've read whole file already.</param>
|
||||
/// <param name="lastAt">Ending index, used for pagination. Omit it for the first request, then initialize to TotalLines returned, and on every following request subtract count that you've used in the previous request from it until you hit 0 or less, which means you've read whole file already.</param>
|
||||
[HttpGet("File")]
|
||||
[ProducesResponseType(typeof(GenericResponse<GenericResponse<LogResponse>>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<ActionResult<GenericResponse>> FileGet(int count = 100, int lastAt = 0) {
|
||||
if (count <= 0) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(count))));
|
||||
}
|
||||
|
||||
if (lastAt < 0) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(lastAt))));
|
||||
}
|
||||
|
||||
if (!Logging.LogFileExists) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(SharedInfo.LogFile))));
|
||||
}
|
||||
|
||||
string[]? lines = await Logging.ReadLogFileLines().ConfigureAwait(false);
|
||||
|
||||
if ((lines == null) || (lines.Length == 0)) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(SharedInfo.LogFile))));
|
||||
}
|
||||
|
||||
if ((lastAt == 0) || (lastAt > lines.Length)) {
|
||||
lastAt = lines.Length;
|
||||
}
|
||||
|
||||
int startFrom = Math.Max(lastAt - count, 0);
|
||||
|
||||
return Ok(new GenericResponse<LogResponse>(new LogResponse(lines.Length, lines[startFrom..lastAt])));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches ASF log in realtime.
|
||||
/// </summary>
|
||||
@@ -52,7 +90,7 @@ public sealed class NLogController : ArchiController {
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GenericResponse<string>>), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
|
||||
public async Task<ActionResult> NLogGet(CancellationToken cancellationToken) {
|
||||
public async Task<ActionResult> Get(CancellationToken cancellationToken) {
|
||||
if (HttpContext == null) {
|
||||
throw new InvalidOperationException(nameof(HttpContext));
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class TwoFactorAuthenticationController : ArchiController {
|
||||
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == Confirmation.EType.Unknown) || !Enum.IsDefined(typeof(Confirmation.EType), request.AcceptedType.Value))) {
|
||||
if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == Confirmation.EType.Unknown) || !Enum.IsDefined(request.AcceptedType.Value))) {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(request.AcceptedType))));
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class TypeController : ArchiController {
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(static property => property.CanRead && (property.GetMethod?.IsPrivate == false))) {
|
||||
foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(static property => property is { CanRead: true, GetMethod.IsPrivate: false })) {
|
||||
JsonPropertyAttribute? jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
|
||||
if (jsonProperty != null) {
|
||||
@@ -95,7 +95,7 @@ public sealed class TypeController : ArchiController {
|
||||
string? valueText = value?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(valueText)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(valueText));
|
||||
ASF.ArchiLogger.LogNullError(valueText);
|
||||
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorObjectIsNull, nameof(valueText))));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ using MvcNewtonsoftJsonOptions = Microsoft.AspNetCore.Mvc.MvcJsonOptions;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -88,7 +89,19 @@ internal sealed class ApiAuthenticationMiddleware {
|
||||
await context.Response.WriteJsonAsync(new GenericResponse<StatusCodeResponse>(false, statusCodeResponse), jsonOptions.Value.SerializerSettings).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static void ClearFailedAuthorizations(object? state = null) => FailedAuthorizations.Clear();
|
||||
internal static void ClearFailedAuthorizations(object? state = null) => FailedAuthorizations.Clear();
|
||||
|
||||
internal static IEnumerable<IPAddress> GetCurrentlyBannedIPs() => FailedAuthorizations.Where(static kv => kv.Value >= MaxFailedAuthorizationAttempts).Select(static kv => kv.Key);
|
||||
|
||||
internal static bool UnbanIP(IPAddress ipAddress) {
|
||||
ArgumentNullException.ThrowIfNull(ipAddress);
|
||||
|
||||
if (!FailedAuthorizations.TryGetValue(ipAddress, out byte attempts) || (attempts < MaxFailedAuthorizationAttempts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return FailedAuthorizations.TryRemove(ipAddress, out _);
|
||||
}
|
||||
|
||||
private async Task<(HttpStatusCode StatusCode, bool Permanent)> GetAuthenticationStatus(HttpContext context) {
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Integration;
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
internal sealed class LocalizationMiddleware {
|
||||
private static readonly ImmutableDictionary<string, string> CultureConversions = new Dictionary<string, string>(2, StringComparer.OrdinalIgnoreCase) {
|
||||
{ "lol-US", SharedInfo.LolcatCultureName },
|
||||
{ "sr-CS", "sr-Latn" }
|
||||
}.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly RequestDelegate Next;
|
||||
|
||||
public LocalizationMiddleware(RequestDelegate next) => Next = next ?? throw new ArgumentNullException(nameof(next));
|
||||
|
||||
[UsedImplicitly]
|
||||
public async Task InvokeAsync(HttpContext context) {
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
RequestHeaders headers = context.Request.GetTypedHeaders();
|
||||
|
||||
IList<StringWithQualityHeaderValue> acceptLanguageHeader = headers.AcceptLanguage;
|
||||
|
||||
if (acceptLanguageHeader.Count == 0) {
|
||||
await Next(context).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool valuesChanged = false;
|
||||
|
||||
for (int i = 0; i < acceptLanguageHeader.Count; i++) {
|
||||
StringSegment language = acceptLanguageHeader[i].Value;
|
||||
|
||||
if (!language.HasValue || string.IsNullOrEmpty(language.Value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CultureConversions.TryGetValue(language.Value, out string? replacement) || string.IsNullOrEmpty(replacement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
acceptLanguageHeader[i] = StringWithQualityHeaderValue.Parse(replacement);
|
||||
valuesChanged = true;
|
||||
}
|
||||
|
||||
if (valuesChanged) {
|
||||
// The getter returns a temporary collection; To make sure our changes are persisted, we need to assign it back
|
||||
headers.AcceptLanguage = acceptLanguageHeader;
|
||||
}
|
||||
|
||||
await Next(context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public sealed class TwoFactorAuthenticationConfirmationsRequest {
|
||||
/// <summary>
|
||||
/// A helper property which works the same as <see cref="AcceptedCreatorIDs" /> but with values written as strings - for javascript compatibility purposes. Use either this one, or <see cref="AcceptedCreatorIDs" />, not both.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = $"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(AcceptedCreatorIDs)}", Required = Required.DisallowNull)]
|
||||
[JsonProperty($"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(AcceptedCreatorIDs)}", Required = Required.DisallowNull)]
|
||||
public ImmutableHashSet<string> SAcceptedCreatorIDs {
|
||||
get => AcceptedCreatorIDs.Select(static creatorID => creatorID.ToString(CultureInfo.InvariantCulture)).ToImmutableHashSet();
|
||||
set {
|
||||
|
||||
@@ -39,6 +39,9 @@ public sealed class GenericResponse<T> : GenericResponse where T : class {
|
||||
public GenericResponse(bool success, string? message) : base(success, message) { }
|
||||
public GenericResponse(bool success, T? result) : base(success) => Result = result;
|
||||
public GenericResponse(bool success, string? message, T? result) : base(success, message) => Result = result;
|
||||
|
||||
[JsonConstructor]
|
||||
private GenericResponse() { }
|
||||
}
|
||||
|
||||
public class GenericResponse {
|
||||
@@ -49,7 +52,7 @@ public class GenericResponse {
|
||||
/// This property will provide exact reason for majority of expected failures.
|
||||
/// </remarks>
|
||||
[JsonProperty]
|
||||
public string Message { get; private set; }
|
||||
public string? Message { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Boolean type that specifies if the request has succeeded.
|
||||
@@ -64,4 +67,7 @@ public class GenericResponse {
|
||||
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
|
||||
Message = !string.IsNullOrEmpty(message) ? message! : success ? "OK" : Strings.WarningFailed;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
protected GenericResponse() { }
|
||||
}
|
||||
|
||||
52
ArchiSteamFarm/IPC/Responses/LogResponse.cs
Normal file
52
ArchiSteamFarm/IPC/Responses/LogResponse.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Responses;
|
||||
|
||||
public sealed class LogResponse {
|
||||
/// <summary>
|
||||
/// Content of the log file which consists of lines read from it - in chronological order.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
[Required]
|
||||
public IReadOnlyList<string> Content { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of lines of the log file returned, can be used as an index for future requests.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
[Required]
|
||||
public int TotalLines { get; private set; }
|
||||
|
||||
internal LogResponse(int totalLines, IReadOnlyList<string> content) {
|
||||
if (totalLines < 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(totalLines));
|
||||
}
|
||||
|
||||
TotalLines = totalLines;
|
||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using ArchiSteamFarm.Core;
|
||||
@@ -44,7 +43,6 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
@@ -103,14 +101,13 @@ internal sealed class Startup {
|
||||
app.UseStaticFiles(
|
||||
new StaticFileOptions {
|
||||
OnPrepareResponse = static context => {
|
||||
if (context.File.Exists && !context.File.IsDirectory && !string.IsNullOrEmpty(context.File.Name)) {
|
||||
if (context.File is { Exists: true, IsDirectory: false } && !string.IsNullOrEmpty(context.File.Name)) {
|
||||
string extension = Path.GetExtension(context.File.Name);
|
||||
|
||||
CacheControlHeaderValue cacheControl = new();
|
||||
|
||||
switch (extension.ToUpperInvariant()) {
|
||||
case ".CSS":
|
||||
case ".JS":
|
||||
case ".CSS" or ".JS":
|
||||
// Add support for SRI-protected static files
|
||||
// SRI requires from us to notify the caller (especially proxy) to avoid modifying the data
|
||||
cacheControl.NoTransform = true;
|
||||
@@ -136,12 +133,6 @@ internal sealed class Startup {
|
||||
}
|
||||
);
|
||||
|
||||
// Add support for additional localization mappings
|
||||
app.UseMiddleware<LocalizationMiddleware>();
|
||||
|
||||
// Add support for localization
|
||||
app.UseRequestLocalization();
|
||||
|
||||
// Use routing for our API controllers, this should be called once we're done with all the static files mess
|
||||
#if !NETFRAMEWORK
|
||||
app.UseRouting();
|
||||
@@ -233,29 +224,6 @@ internal sealed class Startup {
|
||||
// Add support for response compression
|
||||
services.AddResponseCompression();
|
||||
|
||||
// Add support for localization
|
||||
services.AddLocalization();
|
||||
|
||||
services.AddRequestLocalization(
|
||||
static options => {
|
||||
// We do not set the DefaultRequestCulture here, because it will default to Thread.CurrentThread.CurrentCulture in this case, which is set when loading GlobalConfig
|
||||
|
||||
try {
|
||||
CultureInfo lolcatCulture = CultureInfo.CreateSpecificCulture(SharedInfo.LolcatCultureName);
|
||||
|
||||
options.SupportedCultures = options.SupportedUICultures = CultureInfo.GetCultures(CultureTypes.AllCultures).Append(lolcatCulture).ToList();
|
||||
} catch (Exception e) {
|
||||
// Fallback for platforms that do not support qps-Ploc culture
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
options.SupportedCultures = options.SupportedUICultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
|
||||
}
|
||||
|
||||
// The default checks the URI and cookies and only then for headers; ASFs IPC does not use either of the higher priority mechanisms anywhere else and we don't want to start here.
|
||||
options.RequestCultureProviders = new List<IRequestCultureProvider>(1) { new AcceptLanguageHeaderRequestCultureProvider() };
|
||||
}
|
||||
);
|
||||
|
||||
string? ipcPassword = ASF.GlobalConfig != null ? ASF.GlobalConfig.IPCPassword : GlobalConfig.DefaultIPCPassword;
|
||||
|
||||
if (!string.IsNullOrEmpty(ipcPassword)) {
|
||||
@@ -291,7 +259,11 @@ internal sealed class Startup {
|
||||
}
|
||||
);
|
||||
|
||||
options.CustomSchemaIds(static type => type.GetUnifiedName());
|
||||
// We require custom schema IDs due to conflicting type names, choosing the proper one is tricky as there is no good answer and any kind of convention has a potential to create conflict
|
||||
// FullName and Name both do, ToString() for unknown to me reason doesn't, and I don't have courage to call our WebUtilities.GetUnifiedName() better than what .NET ships with (because it isn't)
|
||||
// Let's use ToString() until we find a good enough reason to change it
|
||||
options.CustomSchemaIds(static type => type.ToString());
|
||||
|
||||
options.EnableAnnotations(true, true);
|
||||
|
||||
options.SchemaFilter<CustomAttributesSchemaFilter>();
|
||||
@@ -309,7 +281,8 @@ internal sealed class Startup {
|
||||
Url = new Uri(SharedInfo.LicenseURL)
|
||||
},
|
||||
|
||||
Title = $"{SharedInfo.ASF} API"
|
||||
Title = $"{SharedInfo.AssemblyName} API",
|
||||
Version = SharedInfo.Version.ToString()
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
#if NETFRAMEWORK
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
#endif
|
||||
@@ -38,21 +37,14 @@ namespace ArchiSteamFarm.IPC;
|
||||
internal static class WebUtilities {
|
||||
#if NETFRAMEWORK
|
||||
internal static IMvcCoreBuilder AddControllers(this IServiceCollection services) {
|
||||
if (services == null) {
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
return services.AddMvcCore();
|
||||
}
|
||||
|
||||
internal static IMvcCoreBuilder AddNewtonsoftJson(this IMvcCoreBuilder mvc, Action<MvcJsonOptions> setupAction) {
|
||||
if (mvc == null) {
|
||||
throw new ArgumentNullException(nameof(mvc));
|
||||
}
|
||||
|
||||
if (setupAction == null) {
|
||||
throw new ArgumentNullException(nameof(setupAction));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(mvc);
|
||||
ArgumentNullException.ThrowIfNull(setupAction);
|
||||
|
||||
// Add JSON formatters that will be used as default ones if no specific formatters are asked for
|
||||
mvc.AddJsonFormatters();
|
||||
@@ -61,18 +53,6 @@ internal static class WebUtilities {
|
||||
|
||||
return mvc;
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddRequestLocalization(this IServiceCollection services, Action<RequestLocalizationOptions> action) {
|
||||
if (services == null) {
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (action == null) {
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
}
|
||||
|
||||
return services.Configure(action);
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static string? GetUnifiedName(this Type type) {
|
||||
@@ -113,11 +93,15 @@ internal static class WebUtilities {
|
||||
StreamWriter streamWriter = new(response.Body, Encoding.UTF8);
|
||||
|
||||
await using (streamWriter.ConfigureAwait(false)) {
|
||||
using JsonTextWriter jsonWriter = new(streamWriter) {
|
||||
#pragma warning disable CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
JsonTextWriter jsonWriter = new(streamWriter) {
|
||||
CloseOutput = false
|
||||
};
|
||||
#pragma warning restore CA2000 // False positive, we're actually wrapping it in the using clause below exactly for that purpose
|
||||
|
||||
serializer.Serialize(jsonWriter, value);
|
||||
await using (jsonWriter.ConfigureAwait(false)) {
|
||||
serializer.Serialize(jsonWriter, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
30
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
@@ -969,12 +969,6 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
public static string ActivelyMatchingItemsRound {
|
||||
get {
|
||||
return ResourceManager.GetString("ActivelyMatchingItemsRound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string WarningExcessiveBotsCount {
|
||||
get {
|
||||
return ResourceManager.GetString("WarningExcessiveBotsCount", resourceCulture);
|
||||
@@ -1029,12 +1023,6 @@ namespace ArchiSteamFarm.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
public static string InteractiveConsoleNotAvailable {
|
||||
get {
|
||||
return ResourceManager.GetString("InteractiveConsoleNotAvailable", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string BotGamesToRedeemInBackgroundCount {
|
||||
get {
|
||||
return ResourceManager.GetString("BotGamesToRedeemInBackgroundCount", resourceCulture);
|
||||
@@ -1202,5 +1190,23 @@ namespace ArchiSteamFarm.Localization {
|
||||
return ResourceManager.GetString("PatchingFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string UserInputCryptkey {
|
||||
get {
|
||||
return ResourceManager.GetString("UserInputCryptkey", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ErrorIPNotBanned {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorIPNotBanned", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string WarningNoLicense {
|
||||
get {
|
||||
return ResourceManager.GetString("WarningNoLicense", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
484
ArchiSteamFarm/Localization/Strings.be-BY.resx
Normal file
484
ArchiSteamFarm/Localization/Strings.be-BY.resx
Normal file
@@ -0,0 +1,484 @@
|
||||
<?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="AcceptingTrade" xml:space="preserve">
|
||||
<value>Прыняцце абмену: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="AutoUpdateCheckInfo" xml:space="preserve">
|
||||
<value>ASF будзе аўтаматычна правяраць абнаўленні кожныя {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "24 hours")</comment>
|
||||
</data>
|
||||
<data name="Content" xml:space="preserve">
|
||||
<value>Змест:
|
||||
{0}</value>
|
||||
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
|
||||
</data>
|
||||
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
|
||||
<value>Сканфігураваная {0} уласцівасць няправільная {1}</value>
|
||||
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
|
||||
<value>ASF V{0} сутыкнуўся з фатальнай памылкай, перш чым модуль рэгістрацыі ядра змог запусціцца!</value>
|
||||
<comment>{0} will be replaced by version number</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
|
||||
<value>Выхад з ненулявым кодам памылкі!</value>
|
||||
</data>
|
||||
<data name="ErrorFailingRequest" xml:space="preserve">
|
||||
<value>Памылка запыту да: {0}</value>
|
||||
<comment>{0} will be replaced by URL of the request</comment>
|
||||
</data>
|
||||
<data name="ErrorGlobalConfigNotLoaded" xml:space="preserve">
|
||||
<value>Не ўдалося загрузіць глабальную канфігурацыю. Пераканайцеся, што {0} існуе і з'яўляецца сапраўдным! Выконвайце інструкцыю па наладжванні на вікі, калі вы заблыталіся.</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorIsInvalid" xml:space="preserve">
|
||||
<value>{0} няверны!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Боты не вызначаны. Можа вы забыліся наладзіць ASF? Выконвайце інструкцыю па наладжванні на вікі, калі вы заблыталіся.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} мае значэнне нуль!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorRequestFailedTooManyTimes" xml:space="preserve">
|
||||
<value>Запыт не атрымаўся пасля {0} спроб!</value>
|
||||
<comment>{0} will be replaced by maximum number of tries</comment>
|
||||
</data>
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Не атрымалася праверыць наяўнасць новай версіі!</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Немагчыма працягнуць абнаўленне, таму што няма файлаў, звязаных з запушчанай зараз версіяй! Аўтаматычнае абнаўленне да гэтай версіі немагчыма.</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Атрыманы запыт на ўвод карыстальнікам, але працэс працуе ў рэжыме без інтэрфейсу!</value>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Выхад...</value>
|
||||
</data>
|
||||
<data name="WarningFailed" xml:space="preserve">
|
||||
<value>Не атрымалася!</value>
|
||||
</data>
|
||||
<data name="GlobalConfigChanged" xml:space="preserve">
|
||||
<value>Файл глабальнай канфігурацыі быў зменены!</value>
|
||||
</data>
|
||||
<data name="ErrorGlobalConfigRemoved" xml:space="preserve">
|
||||
<value>Файл глабальнай канфігурацыі быў выдалены!</value>
|
||||
</data>
|
||||
<data name="IgnoringTrade" xml:space="preserve">
|
||||
<value>Ігнараванне абмену: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="LoggingIn" xml:space="preserve">
|
||||
<value>Уваход у {0}...</value>
|
||||
<comment>{0} will be replaced by service's name</comment>
|
||||
</data>
|
||||
<data name="NoBotsAreRunning" xml:space="preserve">
|
||||
<value>Няма працуючых ботаў, выхад...</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>Абнаўленне нашай сесіі!</value>
|
||||
</data>
|
||||
<data name="RejectingTrade" xml:space="preserve">
|
||||
<value>Адхіленне абмену: {0}</value>
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="Restarting" xml:space="preserve">
|
||||
<value>Перазапуск...</value>
|
||||
</data>
|
||||
<data name="Starting" xml:space="preserve">
|
||||
<value>Запуск...</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>Паспяхова завершана!</value>
|
||||
</data>
|
||||
<data name="UnlockingParentalAccount" xml:space="preserve">
|
||||
<value>Разблакіраванне бацькоўскага кантролю...</value>
|
||||
</data>
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Ідзе праверка наяўнасці новай версіі...</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Спампоўка новай версіі: {0} ({1} МБ)... Пакуль чакаеце, падумайце аб ахвяраванні, калі вы шануеце праробленую працу! :)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
<value>Працэс абнаўлення завершаны!</value>
|
||||
</data>
|
||||
<data name="UpdateNewVersionAvailable" xml:space="preserve">
|
||||
<value>Даступна новая версія ASF! Вы можаце абнавіцца да яе самастойна!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IPCReady" xml:space="preserve">
|
||||
<value>Сервер IPC гатовы!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>Запуск сервера IPC...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Гатова!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Усяго ў нас засталося {0} гульняў ({1} карт) для фармавання (засталося ~{2})...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Фармаванне спынена!</value>
|
||||
</data>
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Ігнараванне гэтага запыту, так як пастаянная паўза ўключана!</value>
|
||||
</data>
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>Няма нічога да фармавання на гэтым акаўнце!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>Зараз фарміцца: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>Зараз фарміцца: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Зараз запусціць гульню немагчыма, мы паспрабуем яшчэ раз пазней!</value>
|
||||
</data>
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Яшчэ фарміцца: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>Яшчэ фарміцца: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>Спынена фармаванне: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StoppedIdlingList" xml:space="preserve">
|
||||
<value>Спынена фармаванне: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Невядомая каманда!</value>
|
||||
</data>
|
||||
<data name="WarningCouldNotCheckBadges" xml:space="preserve">
|
||||
<value>Не ўдалося атрымаць інфармацыю аб значках, мы паспрабуем яшчэ раз пазней!</value>
|
||||
</data>
|
||||
<data name="WarningCouldNotCheckCardsStatus" xml:space="preserve">
|
||||
<value>Немагчыма праверыць стан картак для: {0} ({1}), мы паспрабуем яшчэ раз пазней!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="BotAcceptingGift" xml:space="preserve">
|
||||
<value>Прыняцце падарунка: {0}...</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Гэты ўліковы запіс абмежаваны, працэс фармавання недаступны, пакуль абмежаванне не будзе знята!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Статус: {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
|
||||
</data>
|
||||
<data name="BotAddLicenseWithItems" xml:space="preserve">
|
||||
<value>ID: {0} | Статус: {1} | Тавары: {2}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string, {2} will be replaced by list of granted IDs (numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="BotAlreadyRunning" xml:space="preserve">
|
||||
<value>Гэты бот ужо працуе!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorConverting" xml:space="preserve">
|
||||
<value>Пераўтварэнне .maFile у фармат ASF...</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorImportFinished" xml:space="preserve">
|
||||
<value>Паспяхова завершаны імпарт мабільнага аўтэнтыфікатара!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorToken" xml:space="preserve">
|
||||
<value>2FA Токен: {0}</value>
|
||||
<comment>{0} will be replaced by generated 2FA token (string)</comment>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
|
||||
<value>Аўтаматычнае фарменне прыпынена!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
|
||||
<value>Аўтаматычнае фарменне адноўлена!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
|
||||
<value>Аўтаматычнае фарменне ўжо прыпынена!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
|
||||
<value>Аўтаматычнае фарменне ўжо адноўлена!</value>
|
||||
</data>
|
||||
<data name="BotConnected" xml:space="preserve">
|
||||
<value>Падключана да Steam!</value>
|
||||
</data>
|
||||
<data name="BotDisconnected" xml:space="preserve">
|
||||
<value>Адключана ад Steam!</value>
|
||||
</data>
|
||||
<data name="BotDisconnecting" xml:space="preserve">
|
||||
<value>Адключэнне...</value>
|
||||
</data>
|
||||
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
|
||||
<value>Гэты бот не запушчаны, бо ён адключаны ў канфігурацыйным файле!</value>
|
||||
</data>
|
||||
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
|
||||
<value>Памылка TwoFactorCodeMismatch атрымана {0} разоў запар. Альбо вашыя ўліковыя даныя 2FA больш не дзейнічаюць, альбо вашы гадзіны не сінхранізуюцца, спыняю працу!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOff" xml:space="preserve">
|
||||
<value>Выйшлі з Steam: {0}</value>
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOn" xml:space="preserve">
|
||||
<value>Паспяхова ўвайшлі як {0}.</value>
|
||||
<comment>{0} will be replaced by steam ID (number)</comment>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Уваход...</value>
|
||||
</data>
|
||||
<data name="BotLogonSessionReplaced" xml:space="preserve">
|
||||
<value>Здаецца, што гэты акаўнт выкарыстоўваецца ў іншым экзэмпляры ASF, што з'яўляецца недапушчальным, спыняем працу!</value>
|
||||
</data>
|
||||
<data name="BotLootingFailed" xml:space="preserve">
|
||||
<value>Прапанова абмену не атрымалася!</value>
|
||||
</data>
|
||||
<data name="BotLootingMasterNotDefined" xml:space="preserve">
|
||||
<value>Прапанова абмену не можа быць адпраўлена, бо няма карыстальніка з правамі "master"!</value>
|
||||
</data>
|
||||
<data name="BotLootingSuccess" xml:space="preserve">
|
||||
<value>Прапанова абмену паспяхова адпраўлена!</value>
|
||||
</data>
|
||||
<data name="BotSendingTradeToYourself" xml:space="preserve">
|
||||
<value>Вы не можаце адправіць прапанову абмену да сябе!</value>
|
||||
</data>
|
||||
<data name="BotNoASFAuthenticator" xml:space="preserve">
|
||||
<value>У гэтага бота не ўключаны ASF 2FA! Ці не забыліся вы імпартаваць свой аўтэнтыфікатар да ASF 2FA?</value>
|
||||
</data>
|
||||
<data name="BotNotConnected" xml:space="preserve">
|
||||
<value>Гэты бот не падключаны!</value>
|
||||
</data>
|
||||
<data name="BotNotOwnedYet" xml:space="preserve">
|
||||
<value>Яшчэ не мае: {0}</value>
|
||||
<comment>{0} will be replaced by query (string)</comment>
|
||||
</data>
|
||||
<data name="BotOwnedAlreadyWithName" xml:space="preserve">
|
||||
<value>Ужо мае: {0} | {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="BotPointsBalance" xml:space="preserve">
|
||||
<value>Баланс ачкоў: {0}</value>
|
||||
<comment>{0} will be replaced by the points balance value (integer)</comment>
|
||||
</data>
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Перавышаны ліміт частаты запросаў, мы паўторым спробу пасля {0}...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
<value>Паўторнае падключэнне...</value>
|
||||
</data>
|
||||
<data name="BotRedeem" xml:space="preserve">
|
||||
<value>Ключ: {0} | Статус: {1}</value>
|
||||
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string</comment>
|
||||
</data>
|
||||
<data name="BotRedeemWithItems" xml:space="preserve">
|
||||
<value>Ключ: {0} | Статус: {1} | Тавары: {2}</value>
|
||||
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string, {2} will be replaced by list of key-value pairs, separated by a comma</comment>
|
||||
</data>
|
||||
<data name="BotRemovedExpiredLoginKey" xml:space="preserve">
|
||||
<value>Выдалены пратэрмінаваны ключ для ўваходу!</value>
|
||||
</data>
|
||||
<data name="BotStatusNotIdling" xml:space="preserve">
|
||||
<value>Бот нічога не фарміць.</value>
|
||||
</data>
|
||||
<data name="BotStatusLimited" xml:space="preserve">
|
||||
<value>Бот абмежаваны і не можа атрымліваць карткі праз фарменне.</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Падключэнне...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PluginsWarning" xml:space="preserve">
|
||||
<value>Вы загрузілі адзін або некалькі карыстальніцкіх убудоў у ASF. Паколькі мы не можам прапанаваць падтрымку мадыфікаваных канфігурацый, калі ласка, звярніцеся да адпаведных распрацоўнікаў, убудовы якіх вы вырашылі выкарыстоўваць у выпадку ўзнікнення якіх-небудзь праблем.</value>
|
||||
</data>
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>Пачакайце, калі ласка...</value>
|
||||
</data>
|
||||
<data name="EnterCommand" xml:space="preserve">
|
||||
<value>Увядзіце каманду: </value>
|
||||
</data>
|
||||
<data name="Executing" xml:space="preserve">
|
||||
<value>Выкананне...</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Інтэрактыўная кансоль цяпер актыўная, націсніце 'c', каб увайсці ў рэжым каманд.</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="Result" xml:space="preserve">
|
||||
<value>Рэзультат: {0}</value>
|
||||
<comment>{0} will be replaced by generic result of various functions that use this string</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Выпраўленне файлаў ASF...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
@@ -79,7 +79,7 @@
|
||||
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
|
||||
<value>ASF V{0} претърпя критичен срив, преди основния записващ модул, да стартира!</value>
|
||||
<value>ASF V{0} претърпя критичен срив, преди основния записващ модул да стартира!</value>
|
||||
<comment>{0} will be replaced by version number</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
|
||||
@@ -101,7 +101,9 @@
|
||||
<value>{0} е невалиден!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Бота не е настроен. Забравихте ли да проверите конфигурацията на ASF? Следвайте инструкцийте в "wiki" ако срещате трудности.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} е нулев!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
@@ -190,7 +192,7 @@
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Моля, въведете SteamGuard кода, който е бил пратен на Вашият e-mail: </value>
|
||||
<value>Моля, въведете SteamGuard кода, изпратен на Вашият e-mail: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
@@ -222,38 +224,86 @@
|
||||
<value>Не е намерен бот с името {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotStatusOverview" xml:space="preserve">
|
||||
<value>Има {0}/{1} работещи бота, с общо {2} игри ({3} карти) оставащи за "фармене".</value>
|
||||
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to farm, {3} will be replaced by total number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdling" xml:space="preserve">
|
||||
<value>Ботът фарми играта: {0} ({1}, {2} оставащи карти) от общо {3} игри ({4} карти) остават за "фармене" (~{5} оставащо време).</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm, {3} will be replaced by total number of games to farm, {4} will be replaced by total number of cards to farm, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>Ботът фарми игрите: {0} от общо {1} игри ({2} карти) остават за "фармене" (~{3} оставащо време).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>Проверяване на първата страница със значки...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>Проверяване на други страници със значки...</value>
|
||||
</data>
|
||||
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Избран алгоритъм за фармене: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen farming algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Готово!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Остават общо {0} игри ({1} карти) за "фармене" (~{2} оставащо време)...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
<value>Фарменето на карти приключи!</value>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGame" xml:space="preserve">
|
||||
<value>Фарменето на карти завърши: {0} ({1}) след {2} игра!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGames" xml:space="preserve">
|
||||
<value>Фарменето на карти завърши за игрите: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="IdlingStatusForGame" xml:space="preserve">
|
||||
<value>Статус на фарменето за {0} ({1}): {2} карти остават</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Фарменето е спряно!</value>
|
||||
</data>
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Отказване на това желание, тъй като е активирана постоянна пауза!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>Няма нищо за фармене на този акаунт!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>Сега се фарми: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>Сега се фарми: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Игрането не е възможно в момента, ще пробваме по-късно!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Все още се фарми: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>Все още се фарми: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>Фарменето прекратено: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StoppedIdlingList" xml:space="preserve">
|
||||
<value>Фарменето прекратено: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Непозната команда!</value>
|
||||
</data>
|
||||
@@ -268,7 +318,9 @@
|
||||
<value>Приемане на подарък: {0}...</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Този акаунт е ограничен, фарменето е невъзможен докато ограничението не бъде премахнато!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Статус: {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
|
||||
@@ -290,10 +342,18 @@
|
||||
<value>2FA Token: {0}</value>
|
||||
<comment>{0} will be replaced by generated 2FA token (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
|
||||
<value>Автоматичното фармене е паузирано!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
|
||||
<value>Автоматичното фармене е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
|
||||
<value>Автоматичното фармене е вече паузирано!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
|
||||
<value>Автоматичното фармене е вече възобновено!</value>
|
||||
</data>
|
||||
<data name="BotConnected" xml:space="preserve">
|
||||
<value>Свързан към Steam!</value>
|
||||
</data>
|
||||
@@ -350,7 +410,10 @@
|
||||
<value>Вече се притежава: {0} | {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotPointsBalance" xml:space="preserve">
|
||||
<value>Баланс на точките: {0}</value>
|
||||
<comment>{0} will be replaced by the points balance value (integer)</comment>
|
||||
</data>
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Ограничението надвишено, ще повторим след {0} на "отброяване"...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
@@ -369,8 +432,12 @@
|
||||
<data name="BotRemovedExpiredLoginKey" xml:space="preserve">
|
||||
<value>Премахнат изтекъл ключ за вход!</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotStatusNotIdling" xml:space="preserve">
|
||||
<value>Бота не фарми нищо.</value>
|
||||
</data>
|
||||
<data name="BotStatusLimited" xml:space="preserve">
|
||||
<value>Бота е ограничен и не може да "дропне" карти чрез фармене.</value>
|
||||
</data>
|
||||
<data name="BotStatusConnecting" xml:space="preserve">
|
||||
<value>Ботът се свързва към мрежата на Steam.</value>
|
||||
</data>
|
||||
@@ -402,8 +469,12 @@
|
||||
<data name="BotConnectionLost" xml:space="preserve">
|
||||
<value>Връзката с мрежата на Steam е загубена. Повторно свързване...</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAccountFree" xml:space="preserve">
|
||||
<value>Акаунтът вече не е зает: фарменето на карти е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>Акаунтът в момента се ползва: ASF ще възобнови фарменето на карти, когато е свободен...</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Свързване…</value>
|
||||
</data>
|
||||
@@ -441,7 +512,10 @@
|
||||
<value>ASF ще се опита да ползва вашият предпочитан {0} език, но преводът за този език е само {1} завършен. Може би може да ни помогнете да подобрим ASF с превод на вашия език?</value>
|
||||
<comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment>
|
||||
</data>
|
||||
|
||||
<data name="IdlingGameNotPossible" xml:space="preserve">
|
||||
<value>Фарменето на {0} ({1}) е временно забранено, тъй като ASF не е в състояние да "играе" тази игра в момента.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="WarningIdlingGameMismatch" xml:space="preserve">
|
||||
<value>ASF откри несъответствие на ID за {0} ({1}) и ще използва ID на {2} вместо това.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
|
||||
@@ -450,8 +524,12 @@
|
||||
<value>{0} V{1}</value>
|
||||
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
<value>Този акаунт е заключен, фарменето на карти е недостъпно завинаги!</value>
|
||||
</data>
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Бота е заключен и не може да "дропне" карти чрез фармене.</value>
|
||||
</data>
|
||||
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
|
||||
<value>Тази функция е възможна само в headless режим!</value>
|
||||
</data>
|
||||
@@ -462,7 +540,9 @@
|
||||
<data name="ErrorAccessDenied" xml:space="preserve">
|
||||
<value>Достъпът отказан!</value>
|
||||
</data>
|
||||
|
||||
<data name="WarningPreReleaseVersion" xml:space="preserve">
|
||||
<value>Вие използвате версия, която е по-нова от последната версия за актуализация. Моля, имайте предвид, че предварителните версии са посветени на потребителите, които знаят как да докладват за бъгове, да се справят с проблеми и да дават обратна връзка - няма да ви бъде осигурена техническа подръжка.</value>
|
||||
</data>
|
||||
<data name="BotStats" xml:space="preserve">
|
||||
<value>Текущо използване на паметта: {0} MB.
|
||||
Работно време на процеса: {1}</value>
|
||||
@@ -520,10 +600,6 @@
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Прекратен</value>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Съвпадащи общо {0} комплекта този кръг.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Работите с повече ботове от нашите препоръчителни граници ({0}). Имайте предвид, че тази настройка не се поддържа и може да причини различни проблеми свързани със Steam, включително замразяване на акаунта. Вижте често задаваните въпроси за повече подробности.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
@@ -554,10 +630,6 @@
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Интерактивната конзола е вече активна, натиснете 'c' за да влезето в команден режим.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Интерактивната конзола не е налична, защото липсва {0} конфигурирана стойност.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Ботът има {0} игри оставащи в опашката на заден фон.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
@@ -569,7 +641,10 @@
|
||||
<value>Успешно извършени {0} потвърждения!</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>Изчакване до {0}, за да се гарантира, че е може да започне фарменето на карти...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Изтриване на старите файлове след обновление...</value>
|
||||
</data>
|
||||
@@ -579,13 +654,21 @@
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC настройките са били променени!</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>Предложението за размяна {0} е определено като {1} поради {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>Получили сте код за грешна парола {0} пъти поред. Вашата парола за този акаунт най-вероятно е грешна, прекратяване!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
<data name="Result" xml:space="preserve">
|
||||
<value>Резултат: {0}</value>
|
||||
<comment>{0} will be replaced by generic result of various functions that use this string</comment>
|
||||
</data>
|
||||
|
||||
<data name="WarningUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>Опитвате се да подкарате {0} вариант на ASF в неподдържана страна: {1}. Осигурете аргумент за отхвърляне на това твърдение ако наистина знаете какво правите.</value>
|
||||
</data>
|
||||
<data name="WarningUnknownCommandLineArgument" xml:space="preserve">
|
||||
<value>Непознат аргумент в командната линия: {0}</value>
|
||||
<comment>{0} will be replaced by unrecognized command that has been provided</comment>
|
||||
@@ -593,18 +676,59 @@
|
||||
<data name="ErrorConfigDirectoryNotFound" xml:space="preserve">
|
||||
<value>Папката с настройките не може да бъде открита, прекратяване!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotIdlingSelectedGames" xml:space="preserve">
|
||||
<value>Играят се избраните {0}: {1}</value>
|
||||
<comment>{0} will be replaced by internal name of the config property (e.g. "GamesPlayedWhileIdle"), {1} will be replaced by comma-separated list of appIDs that user has chosen</comment>
|
||||
</data>
|
||||
<data name="AutomaticFileMigration" xml:space="preserve">
|
||||
<value>{0} config file ще бъде прехвърлен на последния синтаксис...</value>
|
||||
<comment>{0} will be replaced with the relative path to the affected config file</comment>
|
||||
</data>
|
||||
<data name="WarningWeakIPCPassword" xml:space="preserve">
|
||||
<value>Вашата IPC парола изглежда много слаба. Помислете за избиране на по-сложна за увеличаване на сигурността. Детайли: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Вашата Steam парола за {0} изглежда много слаба. Помислете за избиране на по-сложна за увеличаване на сигурността. Детайли: {1}</value>
|
||||
<comment>{0} will be replaced by the affected bot name, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Вашият криптиращ ключ изглежда много слаб. Помислете за избиране на по-сложна за увеличаване на сигурността. Детайли: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the encryption key being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningTooShortCryptKey" xml:space="preserve">
|
||||
<value>Вашият криптиращ ключ е много къс. Препоръчваме да ползвате някой, който е поне {0} байта (символа) голям.</value>
|
||||
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
|
||||
<value>Ползвате {0} настройка от {1} стойност, но не сте осигурили персонализиран крипто-ключ. Трябва да осигурите персонализиран крипто-ключ за увеличена защита.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "SCrypt"), {1} will be replaced by the name of the property (e.g. "IPCPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForEncryption" xml:space="preserve">
|
||||
<value>Ползвате {0} настройка от {1} стойност, но не сте осигурили персонализиран крипто-ключ. Това напълно нарушава защитата, тъй като ASF е принуден да ползва собствен (познат) ключ. Трябва да осигурите персонализиран крипто-ключ, за да ползвате предлаганата защита от тези настройки.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "AES"), {1} will be replaced by the name of the property (e.g. "SteamPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningRunningAsRoot" xml:space="preserve">
|
||||
<value>Опитвате се да стартирате ASF като администратор. Това предполага значителен риск за защитата на вашата машина, и тъй като ASF не изисква административен достъп, за да работи, Ви препоръчваме да я стартирате като потребител, който НЕ Е администратор, по възможност.</value>
|
||||
</data>
|
||||
<data name="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>Стартирали сте ASF| в неподдържана среда, осигурявайки аргумент, който отхвърля това твърдение. Имайте в предвид, че не осигуряваме никаква подръжка при подобен сценарий и вършите всичко на свой риск. Предупредени сте.</value>
|
||||
</data>
|
||||
<data name="FetchingChecksumFromRemoteServer" xml:space="preserve">
|
||||
<value>Извлича се checksum от отдалечения сървър...</value>
|
||||
</data>
|
||||
<data name="VerifyingChecksumWithRemoteServer" xml:space="preserve">
|
||||
<value>Потвърждава се checksum на сваления файл срещу този на отдалечения сървър...</value>
|
||||
</data>
|
||||
<data name="ChecksumMissing" xml:space="preserve">
|
||||
<value>Отдалечения сървър не знае нищо за версията до която се опитвате да ъпгрейднете. Подобна ситуация е възможна ако версията е публикувана скоро - отказване да се продължи с ъпдейта като допълнителна мярка за сигурност.</value>
|
||||
</data>
|
||||
<data name="ChecksumWrong" xml:space="preserve">
|
||||
<value>Отдалечения сървър отговори с различен checksum, това може да показва корумпирано сваляне или MITM атака - отказване да се продължи с ъпдейта!</value>
|
||||
</data>
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Пачват се ASF файлове...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ StackTrace:
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Zadejte kód autentifikátoru SteamGuard, který byl zaslán na Váš email: </value>
|
||||
<value>Prosím, zadejte ověřovací kód SteamGuard, který byl odeslán na váš e-mail: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
@@ -603,10 +603,6 @@ StackTrace:
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Přerušeno!</value>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Celkem porovnáno {0} sad karet.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Používáte více osobních účtů pro boty, než je náš doporučený limit ({0}). Upozorňujeme, že takové nastavení není podporováno a může způsobovat různé problémy se službou Steam, včetně pozastavení nebo blokací účtů. Pro další informace se podívejte na FAQ.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
@@ -637,10 +633,6 @@ StackTrace:
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Interaktivní konzole je nyní aktivní, napište "c" pro vstup do příkazového režimu.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Interaktivní konzole není dostupná z důvodu chybějící {0} konfigurace.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Bot má {0} zbývajících her ve frontě.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
@@ -701,7 +693,7 @@ StackTrace:
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Vaše Steam heslo pro '{0}' se zdá být slabé. Pro větší zabezpečení zvažte změnu hesla na silnější variantu. Detaily: {1}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
<comment>{0} will be replaced by the affected bot name, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Váš šifrovací klíč se zdá být slabý. Zvažte výběr silnější varianty pro zvýšení bezpečnosti. Detaily: {0}</value>
|
||||
@@ -740,4 +732,7 @@ StackTrace:
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Aktualizace ASF souborů...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -104,7 +104,9 @@ StackTrace:
|
||||
<value>{0} er ikke gyldig!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Ingen bots er defineret. Har du glemt at konfigurere din ASF? Følg 'opsætning'-guiden på wikien, hvis du er forvirret.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} er null!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
@@ -225,37 +227,82 @@ StackTrace:
|
||||
<value>Kunne ikke finde nogen bot der hedder {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotStatusOverview" xml:space="preserve">
|
||||
<value>Der er {0}/{1} bots kørende med en total af {2} spil ({3} kort) tilbage at idle.</value>
|
||||
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to farm, {3} will be replaced by total number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdling" xml:space="preserve">
|
||||
<value>Botten idler spil: {0} ({1}, {2} kort drop tilbage) fra i alt {3} spil ({4} kort) tilbage at idle (~{5} tilbage).</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm, {3} will be replaced by total number of games to farm, {4} will be replaced by total number of cards to farm, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>Botten idler spil: {0} fra en total af {1} spil ({2} kort) tilbage at idle (~{3} tilbage).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>Tjekker første badge side...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>Tjekker andre badge sider...</value>
|
||||
</data>
|
||||
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Valgte idle algoritme: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen farming algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Færdig!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Vi har totalt {0} spil ({1} kort) tilbage at idle (~{2} tilbage)...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
<value>Idling færdig!</value>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGame" xml:space="preserve">
|
||||
<value>Færdiggjort idling: {0} ({1}) efter {2} spilletid!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGames" xml:space="preserve">
|
||||
<value>Færdig med at idle spil: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="IdlingStatusForGame" xml:space="preserve">
|
||||
<value>Idle status for {0} ({1}): {2} kort tilbage</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Idling stoppede!</value>
|
||||
</data>
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Ignorere denne anmodning da permanent pause er aktiveret!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>Vi har ikke noget at idle på denne konto!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>Idler nu: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>Idler nu: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Det er ikke muligt at spille nu, vi vil prøve igen senere!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Idler stadig: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>Idler stadig: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>Stoppede med at idle: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Ukendt kommando!</value>
|
||||
@@ -372,7 +419,9 @@ StackTrace:
|
||||
<data name="BotRemovedExpiredLoginKey" xml:space="preserve">
|
||||
<value>Fjernede udløbet login nøgle!</value>
|
||||
</data>
|
||||
|
||||
<data name="BotStatusNotIdling" xml:space="preserve">
|
||||
<value>Botten idler ikke noget.</value>
|
||||
</data>
|
||||
|
||||
<data name="BotStatusConnecting" xml:space="preserve">
|
||||
<value>Botten er forbundet til Steam-netværket.</value>
|
||||
@@ -525,10 +574,6 @@ Processens oppetid: {1}</value>
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Annulleret!</value>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Matched totalt {0} sæt denne runde.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Du kører flere personlige bots end vores øverst anbefalede grænse ({0}). Vær opmærksom på at denne opsætning ikke er understøttet og kan medføre Steam-relaterede problemer, herunder eventuelt suspendering af konti. Tjek vores FAQ for flere detaljer.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
@@ -559,10 +604,6 @@ Processens oppetid: {1}</value>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Den interaktive konsol er nu aktiv, tast "c" for at skifte til kommando-mode.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Den interaktive konsol den ikke tilgængelig på grund af at {0} mangler en eller flere konfigurationsindstillinger.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Bot har {0} spil tilbage i sin baggrundskø.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
@@ -614,11 +655,18 @@ Processens oppetid: {1}</value>
|
||||
|
||||
|
||||
|
||||
<data name="WarningRunningAsRoot" xml:space="preserve">
|
||||
<value>Du forsøger at køre ASF som administrator (root). Dette medfører en betydelig sikkerhedsrisiko for din maskine, og da ASF ikke kræver root-adgang for dens drift, vi anbefaler at køre det som ikke-administrator bruger hvis det er muligt.</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Retter ASF-filer...</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -195,7 +195,7 @@ StackTrace:
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Bitte geben Sie den Steam Guard Authentifizierungscode ein, welcher Ihnen per E-Mail geschickt wurde: </value>
|
||||
<value>Bitte geben Sie den SteamGuard Authentifizierungstoken ein, der an Ihre E-Mail Adresse geschickt wurde: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
@@ -370,7 +370,7 @@ StackTrace:
|
||||
<value>Bot-Instanz nicht gestartet, weil diese in der Konfigurationsdatei deaktiviert ist!</value>
|
||||
</data>
|
||||
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
|
||||
<value>Der TwoFactorCodeMismatch-Fehlercode wurde {0} Mal in Folge empfangen. Entweder sind Ihre 2FA-Anmeldeinformationen nicht mehr gültig oder die Systemuhr ist nicht synchronisiert. Der Vorgang wird abgebrochen!</value>
|
||||
<value>Der TwoFactorCodeMismatch-Fehlercode wurde {0} Mal in Folge empfangen. Entweder sind Ihre 2FA-Anmedeinformationen nicht mehr gültig oder die Systemuhr ist nicht synchronisiert. Der Vorgang wird abgebrochen!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOff" xml:space="preserve">
|
||||
@@ -445,10 +445,10 @@ StackTrace:
|
||||
<value>Bot stellt eine Verbindung zum Steam-Netzwerk her.</value>
|
||||
</data>
|
||||
<data name="BotStatusNotRunning" xml:space="preserve">
|
||||
<value>Bot läuft nicht.</value>
|
||||
<value>Bot ist nicht in Betrieb.</value>
|
||||
</data>
|
||||
<data name="BotStatusPaused" xml:space="preserve">
|
||||
<value>Bot ist pausiert oder läuft im manuellen Modus.</value>
|
||||
<value>Bot ist pausiert oder wird im manuellen Modus ausgeführt.</value>
|
||||
</data>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot wird zurzeit benutzt.</value>
|
||||
@@ -476,13 +476,13 @@ StackTrace:
|
||||
<value>Benutzerkonto ist nicht mehr in Verwendung: Sammelprozess fortgesetzt!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>Benutzerkonto ist in Verwendung: ASF wird den Sammelprozess fortsetzen, wenn es wieder möglich ist...</value>
|
||||
<value>Benutzerkonto ist in Verwendung: ASF wird den Sammelprozess fortsetzen, sobald diese wieder verfügbar ist...</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Verbinde...</value>
|
||||
</data>
|
||||
<data name="BotHeartBeatFailed" xml:space="preserve">
|
||||
<value>Die Verbindung zum Client konnte nicht getrennt werden. Verzichte auf diese Bot-Instanz!</value>
|
||||
<value>Die Verbindung zum Client konnte nicht getrennt werden. Auf diese Bot-Instanz wird verzichtet!</value>
|
||||
</data>
|
||||
<data name="BotSteamDirectoryInitializationFailed" xml:space="preserve">
|
||||
<value>SteamDirectory konnte nicht initialisiert werden: Die Verbindung mit dem Steam-Netzwerk könnte viel länger dauern als sonst!</value>
|
||||
@@ -491,7 +491,7 @@ StackTrace:
|
||||
<value>Anhalten...</value>
|
||||
</data>
|
||||
<data name="ErrorBotConfigInvalid" xml:space="preserve">
|
||||
<value>Deine Bot-Konfiguration ist ungültig. Bitte überprüfen Sie den Inhalt von {0} und versuchen Sie es erneut!</value>
|
||||
<value>Ihre Bot-Konfiguration ist ungültig. Bitte überprüfen Sie den Inhalt von {0} und versuchen Sie es erneut!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="ErrorDatabaseInvalid" xml:space="preserve">
|
||||
@@ -506,10 +506,10 @@ StackTrace:
|
||||
<value>Bitte lesen Sie den Abschnitt zu unseren Datenschutzrichtlinien im Wiki, wenn Sie wissen möchten was ASF im Detail alles macht!</value>
|
||||
</data>
|
||||
<data name="Welcome" xml:space="preserve">
|
||||
<value>Es sieht so aus, als ob Sie das Programm zum ersten Mal gestartet haben, willkommen!</value>
|
||||
<value>Es sieht so aus, als ob Sie das Programm zum ersten Mal gestartet haben. Willkommen!</value>
|
||||
</data>
|
||||
<data name="ErrorInvalidCurrentCulture" xml:space="preserve">
|
||||
<value>Ihre angegebene CurrentCulture ist ungültig, ASF wird weiterhin mit dem Standard laufen!</value>
|
||||
<value>Ihre angegebene CurrentCulture ist ungültig, ASF wird weiterhin mit dem Standard ausgeführt!</value>
|
||||
</data>
|
||||
<data name="TranslationIncomplete" xml:space="preserve">
|
||||
<value>ASF wird versuchen, Ihre bevorzugte Sprache {0} zu verwenden, jedoch wurde die Übersetzung in dieser Sprache nur zu {1} abgeschlossen. Können Sie uns vielleicht helfen, die ASF-Übersetzung in Ihrer Sprache zu verbessern?</value>
|
||||
@@ -531,7 +531,7 @@ StackTrace:
|
||||
<value>Das Benutzerkonto ist gesperrt, der Sammelprozess ist permanent unmöglich!</value>
|
||||
</data>
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Benutzerkonto ist gesperrt und kann keine Sammelkarten erhalten.</value>
|
||||
<value>Der Bot ist gesperrt und kann keine Sammelkarten erhalten.</value>
|
||||
</data>
|
||||
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
|
||||
<value>Diese Funktion steht nur im Headless-Modus zur Verfügung!</value>
|
||||
@@ -556,7 +556,7 @@ Prozesslaufzeit: {1}</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Fertig mit dem Löschen der Steam-Entdeckungsliste #{0}.</value>
|
||||
<value>Steam-Entdeckungsliste #{0} wurde erfolgreich geleert.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverviewPerGame" xml:space="preserve">
|
||||
@@ -603,10 +603,6 @@ Prozesslaufzeit: {1}</value>
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Abgebrochen!</value>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Insgesamt wurden in diesem Durchgang {0} Set(s) abgeglichen.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Sie verwenden mehr persönliche Bot-Konten als unsere empfohlene Obergrenze ({0}). Bitte bedenken Sie, dass dieses Setup nicht unterstützt wird und verschiedene Steam-bezogene Probleme verursachen kann, einschließlich Kontosperrungen. Weitere Informationen gibt es in unseren FAQs.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
@@ -623,10 +619,10 @@ Prozesslaufzeit: {1}</value>
|
||||
<value>Nichts gefunden!</value>
|
||||
</data>
|
||||
<data name="PluginsWarning" xml:space="preserve">
|
||||
<value>Sie haben ein oder mehrere benutzerdefinierte Plugins in ASF geladen. Da wir keine Unterstützung für modifizierte Setups anbieten können, bitten wir Sie sich im Falle von Problemen an die entsprechenden Entwickler der Plugins zu wenden.</value>
|
||||
<value>Sie haben ein oder mehrere benutzerdefinierte Plugins in ASF geladen. Da wir keine Unterstützung für modifizierte Setups anbieten können, bitten wir Sie, sich im Falle von Problemen an die entsprechenden Entwickler der Plugins zu wenden.</value>
|
||||
</data>
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>Bitte warte...</value>
|
||||
<value>Bitte warten...</value>
|
||||
</data>
|
||||
<data name="EnterCommand" xml:space="preserve">
|
||||
<value>Befehl eingeben: </value>
|
||||
@@ -637,10 +633,6 @@ Prozesslaufzeit: {1}</value>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Interaktive Konsole ist jetzt aktiv, geben Sie 'c' ein, um den Befehlsmodus zu wechseln.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Die interaktive Konsole ist aufgrund der fehlenden Konfigurationseigenschaften {0} nicht verfügbar.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Bot hat {0} Spiele, die in seiner Hintergrundwarteschlange verbleiben.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
@@ -653,14 +645,14 @@ Prozesslaufzeit: {1}</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>Wir warten bis zu {0} um sicherzustellen, dass es möglich ist, den Sammelprozess zu starten...</value>
|
||||
<value>Wir warten bis zu {0}, um sicherzustellen, dass der Sammelprozess gestartet werden kann...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Alte Dateien nach dem Update bereinigen...</value>
|
||||
</data>
|
||||
<data name="BotGeneratingSteamParentalCode" xml:space="preserve">
|
||||
<value>Erstelle Steam-Jugendschutz-Code, das kann einige Zeit dauern. Eventuell solltest du stattdessen den Code in der Konfiguration hinterlegen...</value>
|
||||
<value>Erstelle Steam-Jugendschutz-Code, das kann einige Zeit dauern. Eventuell sollten Sie stattdessen den Code in der Konfiguration hinterlegen...</value>
|
||||
</data>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Die IPC-Konfiguration wurde geändert!</value>
|
||||
@@ -696,46 +688,57 @@ Prozesslaufzeit: {1}</value>
|
||||
<comment>{0} will be replaced with the relative path to the affected config file</comment>
|
||||
</data>
|
||||
<data name="WarningWeakIPCPassword" xml:space="preserve">
|
||||
<value>Dein IPC-Passwort scheint schwach zu sein. Für erhöhte Sicherheit solltest du überlegen, ein Stärkeres auszusuchen. Details: {0}</value>
|
||||
<value>Ihr IPC-Passwort scheint schwach zu sein. Für erhöhte Sicherheit sollten Sie überlegen, ein Stärkeres auszusuchen. Details: {0}Ihr</value>
|
||||
<comment>{0} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Dein Steam-Passwort für '{0}' scheint schwach zu sein. Für erhöhte Sicherheit solltest du überlegen, ein Stärkeres auszusuchen. Details: {1}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
<value>Ihr Steam-Passwort für '{0}' scheint schwach zu sein. Für erhöhte Sicherheit sollten Sie überlegen, ein Stärkeres auszusuchen. Details: {1}</value>
|
||||
<comment>{0} will be replaced by the affected bot name, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Dein kryptografischer Schlüssel scheint schwach zu sein. Für erhöhte Sicherheit solltest du überlegen, einen Stärkeren auszusuchen. Details: {0}</value>
|
||||
<value>Ihr kryptografischer Schlüssel scheint schwach zu sein. Für erhöhte Sicherheit sollten Sie überlegen, einen Stärkeren auszusuchen. Details: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the encryption key being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningTooShortCryptKey" xml:space="preserve">
|
||||
<value>Dein kryptografischer Schlüssel ist zu kurz. Wir empfehlen einen zu verwenden, der mindestens {0} Bytes (Zeichen) lang ist.</value>
|
||||
<value>Ihr kryptografischer Schlüssel ist zu kurz. Wir empfehlen einen zu verwenden, der mindestens {0} Bytes (Zeichen) lang ist.</value>
|
||||
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
|
||||
<value>Du verwendest {0} für {1}, hast aber keinen eigenen kryptografischen Schlüssel mit --cryptkey spezifiziert. Für erhöhte Sicherheit solltest du einen eigenen kryptografischen Schlüssel verwenden.</value>
|
||||
<value>Sie verwenden {0} für {1}, haben aber keinen eigenen kryptografischen Schlüssel mit --cryptkey spezifiziert. Für erhöhte Sicherheit sollten Sie einen eigenen kryptografischen Schlüssel verwenden.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "SCrypt"), {1} will be replaced by the name of the property (e.g. "IPCPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForEncryption" xml:space="preserve">
|
||||
<value>Du verwendest {0} für {1}, hast aber keinen eigenen kryptografischen Schlüssel mit --cryptkey spezifiziert. Dies umgeht den Schutz komplett, da ASF gezwungen ist den internen (öffentlich bekannten) Schlüssel zu verwenden. Du solltest einen eigenen kryptografischen Schlüssel verwenden, um Nutzen von dieser Einstellung zu haben.</value>
|
||||
<value>Sie verwenden {0} für {1}, haben aber keinen eigenen kryptografischen Schlüssel mit --cryptkey spezifiziert. Dies umgeht den Schutz komplett, da ASF gezwungen ist den internen (öffentlich bekannten) Schlüssel zu verwenden. Sie sollten einen eigenen kryptografischen Schlüssel verwenden, um Nutzen von dieser Einstellung zu haben.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "AES"), {1} will be replaced by the name of the property (e.g. "SteamPassword")</comment>
|
||||
</data>
|
||||
|
||||
<data name="WarningRunningAsRoot" xml:space="preserve">
|
||||
<value>Sie versuchen ASF als Administrator (root) auszuführen. Dies stellt ein signifikantes Sicherheitsrisiko für Ihr Gerät dar und da ASF diese Rechte nicht benötigt, unterstützen wir dieses Szenario nicht. Verwenden Sie das Kommandozeilenargument --ignore-unsupported-environment, wenn Sie wirklich wissen, was Sie tun.</value>
|
||||
</data>
|
||||
<data name="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>Du verwendest ASF in einer nicht unterstützten Umgebung und verwendest das Argument --ignore-unsupported-environment. Bitte beachte, dass wir für dieses Szenario keinerlei Unterstützung anbieten und du das Risiko vollständig auf dich selber nehmen musst. Du wurdest gewarnt.</value>
|
||||
<value>Sie nutzen ASF in einer nicht unterstützten Umgebung und verwenden das Argument --ignore-unsupported-environment. Bitte beachten Sie, dass wir für dieses Szenario keinerlei Unterstützung anbieten und Sie das Risiko vollständig bei Ihnen liegt. Sie wurden gewarnt.</value>
|
||||
</data>
|
||||
<data name="FetchingChecksumFromRemoteServer" xml:space="preserve">
|
||||
<value>Rufe Prüfsumme vom ASF Server ab...</value>
|
||||
<value>Rufe Prüfsumme vom Server ab...</value>
|
||||
</data>
|
||||
<data name="VerifyingChecksumWithRemoteServer" xml:space="preserve">
|
||||
<value>Verifiziere Prüfsumme der heruntergeladenen Datei mmit der des ASF Servers...</value>
|
||||
<value>Verifiziere Prüfsumme der heruntergeladenen Datei mit der des ASF Servers...</value>
|
||||
</data>
|
||||
<data name="ChecksumMissing" xml:space="preserve">
|
||||
<value>Der Server weiß nichts von der Version, zu der wir versuchen zu aktualisieren. Diese Situation ist möglich wenn die Version erst kürzlich veröffentlicht wurde - Wir weigern uns als zusätzliche Sicherheitsmaßnahme das Updateprozedere zum jetzigen Zeitpunkt durchzuführen.</value>
|
||||
<value>Die angeforderte Version, zu der wir versuchen zu aktualisieren, ist auf dem Server nicht Verfügbar. Diese Situation ist möglich, wenn die Version erst kürzlich veröffentlicht wurde. Als zusätzliche Sicherheitsmaßnahme wird das Updateprozedere zum jetzigen Zeitpunkt nicht fortgesetzt.</value>
|
||||
</data>
|
||||
<data name="ChecksumWrong" xml:space="preserve">
|
||||
<value>Der Server antwortete mit einer unterschiedlichen Prüfsumme. Dies kann einen korrupten Download oder eine MITM-Attacke indizieren; Wir weigern uns mit dem Update fortzufahren!</value>
|
||||
<value>Der Server antwortete mit einer unterschiedlichen Prüfsumme. Dies kann einen korrupten Download oder eine MITM-Attacke indizieren; Das Update wird abgebrochen!</value>
|
||||
</data>
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Update ASF-Dateien...</value>
|
||||
<value>Aktualisiere ASF-Dateien...</value>
|
||||
</data>
|
||||
<data name="UserInputCryptkey" xml:space="preserve">
|
||||
<value>Bitte gib deinen kryptographischen Schlüssel ein: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="ErrorIPNotBanned" xml:space="preserve">
|
||||
<value>Die IP-Adresse {0} ist nicht gesperrt!</value>
|
||||
<comment>{0} will be replaced by an IP address which was requested to be unbanned from using IPC</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -132,10 +132,10 @@ StackTrace:
|
||||
<value>Λήφθηκε αίτημα για είσοδο από τον χρήστη, αλλά η διεργασία εκτελείται σε σιωπηλή λειτουργία!</value>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>Έξοδος...</value>
|
||||
<value>Γίνεται έξοδος...</value>
|
||||
</data>
|
||||
<data name="WarningFailed" xml:space="preserve">
|
||||
<value>Αποτυχία!</value>
|
||||
<value>Απέτυχε!</value>
|
||||
</data>
|
||||
<data name="GlobalConfigChanged" xml:space="preserve">
|
||||
<value>Το αρχείο γενικής διαμόρφωσης έχει αλλάξει!</value>
|
||||
@@ -195,7 +195,7 @@ StackTrace:
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Εισάγετε τον κωδικό Steam Guard που έχει αποσταλεί στο e-mail σας: </value>
|
||||
<value>Παρακαλώ εισάγετε τον κωδικό SteamGuard που στάλθηκε στο e-mail σας: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
@@ -603,10 +603,6 @@ StackTrace:
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Ακυρώθηκε!</value>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
|
||||
<value>Ο τελικός αριθμός συνόλων που έχουν ταιριάξει είναι {0}, αυτόν τον γύρο.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Τρέχετε περισσότερους προσωπικούς λογαριασμούς bot από το ανώτατο συνιστώμενο όριο ({0}). Λάβετε γνώση ότι αυτή η ρύθμιση δεν υποστηρίζεται και μπορεί να προκαλέσει διάφορα προβλήματα που σχετίζονται με το Steam, συμπεριλαμβανομένων των αναστολών λογαριασμού. Ελέγξτε τις Συχνές Ερωτήσεις για περισσότερες λεπτομέρειες.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
@@ -637,10 +633,6 @@ StackTrace:
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Η διαδραστική κονσόλα είναι ενεργή, πατήστε 'c' για να εισάγετε εντολή.</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
|
||||
<value>Η διαδραστική κονσόλα δεν είναι διαθέσιμη διότι λείπει η ιδιότητα {0} από το αρχείο διαμόρφωσης.</value>
|
||||
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Το bot έχει {0} παιχνίδια που απομένουν στην ουρά.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
@@ -701,7 +693,7 @@ StackTrace:
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Ο κωδικός πρόσβασης Steam για το '{0}' φαίνεται να είναι αδύναμος. Εξετάστε το ενδεχόμενο να επιλέξετε έναν ισχυρότερο για αυξημένη ασφάλεια. Λεπτομέρειες: {1}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
<comment>{0} will be replaced by the affected bot name, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Το κλειδί κρυπτογράφησής σας φαίνεται να είναι αδύναμο. Εξετάστε το ενδεχόμενο να επιλέξετε ένα ισχυρότερο για αυξημένη ασφάλεια. Λεπτομέρειες: {0}</value>
|
||||
@@ -740,4 +732,13 @@ StackTrace:
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Διόρθωση αρχείων ASF...</value>
|
||||
</data>
|
||||
<data name="UserInputCryptkey" xml:space="preserve">
|
||||
<value>Παρακαλώ εισάγετε τα κρυπτοκλειδιά σας: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="ErrorIPNotBanned" xml:space="preserve">
|
||||
<value>Η διεύθυνση IP {0} δεν έχει αποκλειστεί!</value>
|
||||
<comment>{0} will be replaced by an IP address which was requested to be unbanned from using IPC</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user