Compare commits

..

613 Commits

Author SHA1 Message Date
Łukasz Domeradzki
2bbf197108 Add extra synchronization to IPC state management
Thanks @ezhevita
2025-09-08 23:39:29 +02:00
Łukasz Domeradzki
5d2665207a Handle one more case of Steam being down
Thanks @ezhevita
2025-09-08 23:30:11 +02:00
Łukasz Domeradzki
8618b01800 Fix for edge case of MatchableTypes being empty
Thanks @ezhevita
2025-09-08 23:17:23 +02:00
Łukasz Domeradzki
d59d51ae33 Bump 2025-09-08 22:51:03 +02:00
Łukasz Domeradzki
a313a19cec Add support for machine name 2025-09-08 22:50:23 +02:00
Łukasz Domeradzki
c5f7565ac0 Misc 2025-09-08 03:42:12 +02:00
Łukasz Domeradzki
f52486528b Cache generated swagger.json by default 2025-09-08 03:29:17 +02:00
Łukasz Domeradzki
d17399d98b Misc 2025-09-08 02:42:33 +02:00
Łukasz Domeradzki
1eadaad9bd Fix rp command not working with badge upgrades
Of course volvo had to use different endpoint for that, it'd be too easy otherwise
2025-09-08 02:41:31 +02:00
ArchiBot
22e424a78c Automatic translations update 2025-09-07 02:22:59 +00:00
renovate[bot]
712b57e46b chore(deps): update asf-ui digest to 96606b8 2025-09-06 08:37:05 +00:00
renovate[bot]
bc914f0a09 chore(deps): update asf-ui digest to 64ee49e 2025-09-05 21:29:58 +00:00
renovate[bot]
1da2ccbb1c chore(deps): update asf-ui digest to 4e72d57 2025-09-05 03:11:25 +00:00
Łukasz Domeradzki
c58d802c30 Enable immutable releases 2025-09-05 00:43:05 +02:00
Łukasz Domeradzki
8cdd5c69e6 Remove workaround after JB fixes 2025-09-04 23:55:41 +02:00
renovate[bot]
2b61e80336 chore(deps): update ncipollo/release-action action to v1.20.0 2025-09-04 20:24:07 +00:00
renovate[bot]
d831c47fcd chore(deps): update crowdin/github-action action to v2.11.0 2025-09-04 17:11:33 +00:00
renovate[bot]
53955cb9d2 chore(deps): update dependency mstest to 3.10.4 2025-09-04 15:59:08 +00:00
renovate[bot]
2d9c68c10a chore(deps): update dependency jetbrains.annotations.sources to 2025.2.2 2025-09-04 15:15:48 +00:00
renovate[bot]
c987db7941 chore(deps): update asf-ui digest to 62ed8b8 2025-09-04 12:33:08 +00:00
Łukasz Domeradzki
b1d4eb5c8c Cleanup obsolete parts (#3471) 2025-09-04 14:32:34 +02:00
renovate[bot]
c929efbf24 chore(deps): update actions/setup-node action to v5 (#3478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 14:32:13 +02:00
renovate[bot]
0c3f379fb3 chore(deps): update actions/setup-dotnet action to v5 (#3477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 14:32:03 +02:00
Łukasz Domeradzki
2d79ae9e7a Bump 2025-09-04 13:46:33 +02:00
ArchiBot
d0ac9b3a94 Automatic translations update 2025-09-04 02:20:39 +00:00
ArchiBot
719824b518 Automatic translations update 2025-09-01 02:32:04 +00:00
ArchiBot
f6f290878a Automatic translations update 2025-08-31 02:23:59 +00:00
renovate[bot]
4ee9bfc450 chore(deps): update actions/attest-build-provenance action to v3 (#3476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 10:38:20 +02:00
Łukasz Domeradzki
d3f45fc33d JetBrains.Annotations.Sources updates 2025-08-29 09:22:54 +02:00
renovate[bot]
ee4cefc813 chore(deps): update dependency mstest to 3.10.3 (#3475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 09:42:40 +02:00
renovate[bot]
4b7f570ba5 chore(deps): update asf-ui digest to 25f5d5e 2025-08-26 06:11:27 +00:00
renovate[bot]
6981018003 chore(deps): update dependency scalar.aspnetcore to 2.7.2 2025-08-26 00:53:57 +00:00
ArchiBot
380f27a0bc Automatic translations update 2025-08-25 02:26:46 +00:00
Łukasz Domeradzki
d489bebb85 Misc 2025-08-24 19:09:03 +02:00
Łukasz Domeradzki
2df1c439c4 Misc optimization 2025-08-24 15:04:20 +02:00
renovate[bot]
83ac485f19 chore(deps): update asf-ui digest to 75a61d2 2025-08-24 06:15:00 +00:00
ArchiBot
1c6bf2e828 Automatic translations update 2025-08-24 02:30:35 +00:00
renovate[bot]
bfdab76d71 chore(deps): update crowdin/github-action action to v2.10.0 (#3472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-23 08:42:03 +00:00
ArchiBot
90dba00c07 Automatic translations update 2025-08-23 02:22:51 +00:00
renovate[bot]
1d38521819 chore(deps): update dependency scalar.aspnetcore to 2.7.0 2025-08-22 22:57:31 +00:00
ArchiBot
bc8b56018d Automatic translations update 2025-08-22 02:24:26 +00:00
renovate[bot]
073b22de40 chore(deps): update wiki digest to 44fb363 2025-08-21 19:39:18 +00:00
Łukasz Domeradzki
8bc8efe6c0 Bump 2025-08-21 13:06:33 +02:00
Łukasz Domeradzki
fe80d3029b Add support for GamingDeviceType, kill OnlinePreferences 2025-08-21 13:01:38 +02:00
ArchiBot
86410d4407 Automatic translations update 2025-08-20 02:23:58 +00:00
ArchiBot
aba19cf986 Automatic translations update 2025-08-19 02:25:24 +00:00
renovate[bot]
0a2ca64bf7 chore(deps): update wiki digest to 396caad 2025-08-18 11:30:00 +00:00
ArchiBot
4761393e88 Automatic translations update 2025-08-18 02:35:59 +00:00
renovate[bot]
298026491a chore(deps): update wiki digest to ee43c97 2025-08-17 16:49:10 +00:00
ArchiBot
2fe2c47289 Automatic translations update 2025-08-17 02:34:57 +00:00
Łukasz Domeradzki
2d968c6c14 Don't generate or publish xml documentation files by default 2025-08-16 14:05:06 +02:00
renovate[bot]
0708a4a1e2 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.14.0 2025-08-16 02:28:43 +00:00
renovate[bot]
d2c234db1f chore(deps): update asf-ui digest to 41d408b 2025-08-15 06:29:07 +00:00
ArchiBot
d2b7ffba3b Automatic translations update 2025-08-15 02:31:09 +00:00
Łukasz Domeradzki
ff2a2a728d Use new C# features for serializable files 2025-08-13 22:15:41 +02:00
ArchiBot
2b43cec8fe Automatic translations update 2025-08-13 02:29:47 +00:00
renovate[bot]
6359014017 chore(deps): update asf-ui digest to 63dc959 2025-08-12 19:12:15 +00:00
renovate[bot]
d305c3d6e5 chore(deps): update dependency mstest to 3.10.2 2025-08-12 09:38:54 +00:00
renovate[bot]
9b5c5f39a4 chore(deps): update asf-ui digest to f3053c2 2025-08-11 20:40:55 +00:00
renovate[bot]
627f88508a chore(deps): update dependency scalar.aspnetcore to 2.6.9 2025-08-11 19:49:36 +00:00
renovate[bot]
6e6f2c5675 chore(deps): update actions/checkout action to v5 (#3465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 21:49:11 +02:00
renovate[bot]
7d5aaed8fb chore(deps): update actions/checkout action to v4.3.0 2025-08-11 13:07:56 +00:00
renovate[bot]
eac98c4846 chore(deps): update dependency nlog.web.aspnetcore to 6.0.3 2025-08-10 20:55:16 +00:00
Łukasz Domeradzki
f048513338 Misc optimization 2025-08-10 22:02:00 +02:00
Łukasz Domeradzki
4780031b87 Misc 2025-08-10 03:05:24 +02:00
Łukasz Domeradzki
f54d30ff42 Misc 2025-08-10 02:38:37 +02:00
renovate[bot]
17e97afb97 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.13.1 2025-08-09 04:27:32 +00:00
Łukasz Domeradzki
d79db74385 Clean unused code
It seems this call is pointless, if trade check timer is not null, it's already running, as we never initialize it without enabling it too.
2025-08-09 03:22:48 +02:00
Łukasz Domeradzki
c412a47139 Optimize memory allocations when dealing with Steam items
This removes totally unnecessary allocation of asset and description when we already have it in the response and merely initializing to correct reference.
2025-08-08 23:10:35 +02:00
Łukasz Domeradzki
2387f2462e Update ArchiSteamFarm.sln.DotSettings 2025-08-08 20:51:09 +02:00
Łukasz Domeradzki
6bd2e088fc Misc 2025-08-08 19:37:57 +02:00
Łukasz Domeradzki
b4874a05c6 Code cleanups 2025-08-08 19:32:32 +02:00
Łukasz Domeradzki
a77586ec95 Misc 2025-08-08 17:49:04 +02:00
Łukasz Domeradzki
ed9efe8d72 Misc 2025-08-08 01:05:46 +02:00
Łukasz Domeradzki
f7431b0b10 Bump 2025-08-08 01:04:34 +02:00
Łukasz Domeradzki
a340de9da8 Top commit of all time
This is how you avoid doing breaking changes, kids
2025-08-08 01:02:50 +02:00
renovate[bot]
bf11cf8a7b chore(deps): update asf-ui digest to 5c8cf52 2025-08-06 07:20:38 +00:00
renovate[bot]
b4dc8b493a chore(deps): update actions/download-artifact action to v5 (#3462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 09:19:41 +02:00
renovate[bot]
ce1cf6f4b1 chore(deps): update dependency mstest to 3.10.1 2025-08-05 20:32:28 +00:00
renovate[bot]
8d6422e8b4 chore(deps): update dotnet monorepo to 9.0.8 2025-08-05 01:55:47 +00:00
renovate[bot]
b3c28266cb chore(deps): update docker/login-action action to v3.5.0 2025-08-04 15:28:12 +00:00
Łukasz Domeradzki
074f2e343f Misc optimization 2025-08-03 22:49:50 +02:00
renovate[bot]
8f6f7334a0 chore(deps): update dependency scalar.aspnetcore to 2.6.8 2025-08-03 17:30:21 +00:00
Łukasz Domeradzki
b467c22baa Misc 2025-08-03 15:19:49 +02:00
Łukasz Domeradzki
15d3dd8c74 Bump 2025-08-03 15:12:37 +02:00
Łukasz Domeradzki
2010f3e455 Bump 2025-08-03 15:12:19 +02:00
Łukasz Domeradzki
cc95917d53 Misc cleanups 2025-08-03 15:12:05 +02:00
Łukasz Domeradzki
f918fbb504 Make /Api/NLog/File more flexible in terms of log file location 2025-08-03 15:05:02 +02:00
Łukasz Domeradzki
6d4d644900 Add support for detecting history target in wrapper 2025-08-03 14:24:10 +02:00
ArchiBot
a046af7b8d Automatic translations update 2025-08-03 02:43:27 +00:00
renovate[bot]
bde00df3ad chore(deps): update dependency scalar.aspnetcore to 2.6.7 2025-08-02 19:00:59 +00:00
renovate[bot]
d1080bbb42 chore(deps): update dependency scalar.aspnetcore to 2.6.6 2025-08-02 11:15:01 +00:00
renovate[bot]
875d9be7e5 chore(deps): update asf-ui digest to f6e8455 2025-08-02 04:51:40 +00:00
ArchiBot
e683b3b9ad Automatic translations update 2025-08-02 02:33:10 +00:00
renovate[bot]
199e098704 chore(deps): update asf-ui digest to 73d10a1 2025-08-01 22:26:42 +00:00
renovate[bot]
b4cdfa3011 chore(deps): update crowdin/github-action action to v2.9.1 2025-08-01 10:07:26 +00:00
ArchiBot
92bfeb96e2 Automatic translations update 2025-08-01 02:44:48 +00:00
ArchiBot
f4c9c9de1a Automatic translations update 2025-07-31 02:37:08 +00:00
renovate[bot]
e60293a0ce chore(deps): update dependency scalar.aspnetcore to 2.6.5 2025-07-30 01:14:46 +00:00
renovate[bot]
5659818313 chore(deps): update dependency mstest to 3.10.0 (#3458)
* chore(deps): update dependency mstest to 3.10.0

* Adapt to new analyzers

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>
2025-07-30 03:14:13 +02:00
renovate[bot]
1de5bfd809 chore(deps): update asf-ui digest to 11faf95 2025-07-29 04:46:21 +00:00
Łukasz Domeradzki
0fa3b25f12 Bump 2025-07-28 12:21:55 +02:00
Łukasz Domeradzki
02f505d962 Change null purchase_receipt_info to BadResponse 2025-07-28 12:17:10 +02:00
Łukasz Domeradzki
abdb303069 Fix for edge case that is more edge than nowadays teenagers 2025-07-28 12:03:45 +02:00
Łukasz Domeradzki
c1e5cd5d59 Add fix against null purchase_receipt_info 2025-07-28 11:57:04 +02:00
renovate[bot]
7ea0dda562 chore(deps): update asf-ui digest to cd42a02 2025-07-26 02:52:18 +00:00
ArchiBot
947e63fc61 Automatic translations update 2025-07-26 02:32:34 +00:00
Łukasz Domeradzki
882e561416 Bump 2025-07-25 13:49:41 +02:00
Łukasz Domeradzki
1e531a7259 Misc reorder 2025-07-25 13:49:12 +02:00
Outzzz
2967ae167b public api (#3456) 2025-07-25 13:47:57 +02:00
ArchiBot
089840438d Automatic translations update 2025-07-25 02:35:53 +00:00
renovate[bot]
bcd9ae047e chore(deps): update dependency opentelemetry.exporter.prometheus.aspnetcore to 1.12.0-beta.1 2025-07-24 15:55:39 +00:00
renovate[bot]
20de66a3be chore(deps): update wiki digest to ecb0e1f 2025-07-24 15:07:18 +00:00
renovate[bot]
7587f5f07a chore(deps): update dependency microsoft.codeanalysis.resxsourcegenerator to v5 (#3454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 17:06:40 +02:00
Łukasz Domeradzki
4ad450f0ba Update renovate.json5 2025-07-24 15:22:14 +02:00
Łukasz Domeradzki
2883f42a19 Update renovate.json5
As per https://github.com/renovatebot/renovate/discussions/37142
2025-07-24 15:17:13 +02:00
Łukasz Domeradzki
d788efbaf6 Workaround Jetbrains.Annotations issues 2025-07-24 15:06:58 +02:00
Łukasz Domeradzki
ca0bd87357 Move from JetBrains.Annotations to JetBrains.Annotations.Sources 2025-07-24 14:05:07 +02:00
renovate[bot]
e246d0dfa0 chore(deps): update asf-ui digest to 9869857 2025-07-24 08:46:15 +00:00
renovate[bot]
f93c319427 chore(deps): update asf-ui digest to d9173d5 2025-07-23 23:04:20 +00:00
renovate[bot]
eb591e3b4a chore(deps): update dependency jetbrains.annotations to v2025 (#3448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-23 00:44:38 +02:00
renovate[bot]
37d7f14148 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.13.0 2025-07-21 23:36:17 +00:00
renovate[bot]
86b1814698 chore(deps): update dependency scalar.aspnetcore to 2.6.4 2025-07-21 13:35:49 +00:00
renovate[bot]
d15fae3674 chore(deps): update dependency nlog.web.aspnetcore to 6.0.2 2025-07-20 17:40:12 +00:00
Łukasz Domeradzki
6d3a6168d2 Bump 2025-07-19 14:41:57 +02:00
Łukasz Domeradzki
0467dc1da3 Make trade message disappear for all non-steam appids 2025-07-19 14:36:03 +02:00
Łukasz Domeradzki
d81e8d2de0 Bump 2025-07-19 13:22:05 +02:00
Łukasz Domeradzki
ec59e2e556 Add support for new trade messages acknowledge 2025-07-18 09:52:31 +02:00
ArchiBot
cfdb8c06f3 Automatic translations update 2025-07-18 02:35:51 +00:00
Łukasz Domeradzki
32bf5cd0a7 Bump 2025-07-17 13:45:20 +02:00
Łukasz Domeradzki
a0401a3962 Improve efficiency of MatchActively
Trade hold duration check made sense, but back when we were fetching inventories ourselves. Now, it's much better to find match first, as we have the full data loaded, and only if match is found, check user next.
2025-07-17 13:40:35 +02:00
renovate[bot]
4def44ed1e chore(deps): update dependency scalar.aspnetcore to 2.6.3 2025-07-17 03:52:21 +00:00
ArchiBot
bd472b68a1 Automatic translations update 2025-07-17 02:34:52 +00:00
renovate[bot]
142bac7275 chore(deps): update asf-ui digest to 1d94b49 2025-07-16 04:44:45 +00:00
ArchiBot
80d4ab1dea Automatic translations update 2025-07-16 02:34:20 +00:00
Outzzz
481c995481 new confirmation type (#3442) 2025-07-15 18:56:19 +02:00
ArchiBot
76e3059b34 Automatic translations update 2025-07-14 02:36:38 +00:00
renovate[bot]
9c6157563e chore(deps): update dependency scalar.aspnetcore to 2.6.1 2025-07-13 18:02:53 +00:00
renovate[bot]
c362900290 chore(deps): update asf-ui digest to 0822736 2025-07-12 03:10:35 +00:00
ArchiBot
1573707d39 Automatic translations update 2025-07-12 02:33:47 +00:00
ArchiBot
acce592f02 Automatic translations update 2025-07-11 02:34:06 +00:00
renovate[bot]
24cc03e4ed chore(deps): update asf-ui digest to 75cb11d 2025-07-10 22:00:47 +00:00
renovate[bot]
aec1f45023 chore(deps): update dependency scalar.aspnetcore to 2.6.0 2025-07-10 18:01:49 +00:00
renovate[bot]
e208a3cce0 chore(deps): update crowdin/github-action action to v2.9.0 2025-07-10 11:33:24 +00:00
ArchiBot
2f275e19bd Automatic translations update 2025-07-10 02:32:36 +00:00
renovate[bot]
ac8f12f438 chore(deps): update asf-ui digest to 7a782d7 2025-07-09 23:58:29 +00:00
renovate[bot]
c14580965f chore(deps): update wiki digest to c122ded 2025-07-09 17:02:47 +00:00
renovate[bot]
436a5ab3f9 chore(deps): update dotnet monorepo to 9.0.7 2025-07-09 04:24:15 +00:00
ArchiBot
3e1fde4515 Automatic translations update 2025-07-09 02:31:52 +00:00
renovate[bot]
9beec69e85 chore(deps): update asf-ui digest to 5c24f54 2025-07-08 05:00:15 +00:00
ArchiBot
a1116a87df Automatic translations update 2025-07-08 02:31:32 +00:00
renovate[bot]
cd4bc01d66 chore(deps): update dependency scalar.aspnetcore to 2.5.6 2025-07-07 20:07:16 +00:00
renovate[bot]
7e30601246 chore(deps): update wiki digest to 70b21c1 2025-07-07 13:06:35 +00:00
ArchiBot
708b3792c9 Automatic translations update 2025-07-07 02:34:03 +00:00
renovate[bot]
3c52408579 chore(deps): update wiki digest to f30f4d5 2025-07-06 12:35:34 +00:00
ArchiBot
ea5f729d58 Automatic translations update 2025-07-06 02:36:06 +00:00
renovate[bot]
4d3f7f0b5a chore(deps): update wiki digest to 522bba8 2025-07-05 14:26:30 +00:00
Łukasz Domeradzki
078f6eabdd Bump 2025-07-05 16:25:46 +02:00
Łukasz Domeradzki
6d6da98e8c Bump 2025-07-05 16:05:31 +02:00
renovate[bot]
49f1910425 chore(deps): update dependency nlog.web.aspnetcore to 6.0.1 2025-07-05 13:40:07 +00:00
renovate[bot]
4ba034f988 chore(deps): update dependency nlog.web.aspnetcore to v6 (#3438)
* chore(deps): update dependency nlog.web.aspnetcore to v6

* Adapt ASF to NLog v6

* Use another approach for NLog file archiving

* Use cached ${currentdir} to improve performance

* Misc fix

* Update suffix format

* Misc

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>
2025-07-05 15:39:37 +02:00
ArchiBot
e93ea74def Automatic translations update 2025-07-05 02:26:37 +00:00
Łukasz Domeradzki
5dadbe9090 Allow plugins to initialize new configs with some changed properties 2025-07-03 13:12:10 +02:00
renovate[bot]
e3c1a8c5fb chore(deps): update asf-ui digest to 545f9e1 2025-07-02 23:39:29 +00:00
Łukasz Domeradzki
8e50e9553a Misc fix 2025-07-02 16:55:09 +02:00
Łukasz Domeradzki
ed47c8268b Add optional cookies to WebAPI base 2025-07-02 08:57:44 +02:00
Łukasz Domeradzki
e0cdeaf09e Bump 2025-07-01 16:14:16 +02:00
Łukasz Domeradzki
fc198d6eae Update MaxItemsInSingleInventoryRequest 2025-07-01 16:13:47 +02:00
ArchiBot
977816baa9 Automatic translations update 2025-07-01 02:37:39 +00:00
renovate[bot]
758c0a0385 chore(deps): update asf-ui digest to b984a9d 2025-06-30 07:31:35 +00:00
ArchiBot
e4d7e9dda7 Automatic translations update 2025-06-30 02:33:41 +00:00
renovate[bot]
baadfee9e0 chore(deps): update ncipollo/release-action action to v1.18.0 2025-06-29 21:11:10 +00:00
renovate[bot]
25620603b5 chore(deps): update ncipollo/release-action action to v1.17.0 2025-06-29 06:24:51 +00:00
ArchiBot
a5e306ccae Automatic translations update 2025-06-29 02:36:53 +00:00
Łukasz Domeradzki
e09801442e Bump 2025-06-28 13:11:58 +02:00
Łukasz Domeradzki
08fb3ccb76 Fix serializable files not always getting written on change
In some cases, STJ might decide to replace the object rather than populating it. This will work for majority of properties and use cases, however, we actually set up events on some properties to notify us back if they change during runtime. That event registration did not work properly, as the object was replaced with the new, that did not have appropriate listeners set up.

Populate rather than replacing those selected properties, which fixes the problem.
2025-06-28 13:10:13 +02:00
ArchiBot
e9887cf89e Automatic translations update 2025-06-28 02:26:50 +00:00
renovate[bot]
de33bd057f chore(deps): update asf-ui digest to 49b98a8 2025-06-27 04:14:17 +00:00
ArchiBot
e04d37a694 Automatic translations update 2025-06-27 02:29:29 +00:00
renovate[bot]
6e7a1ea09b chore(deps): update dependency scalar.aspnetcore to 2.5.3 2025-06-26 16:37:37 +00:00
Łukasz Domeradzki
08b2c3186d Misc 2025-06-26 14:23:12 +02:00
renovate[bot]
6ca3989635 chore(deps): update dependency scalar.aspnetcore to 2.5.1 2025-06-26 02:58:11 +00:00
ArchiBot
c3019bed16 Automatic translations update 2025-06-26 02:29:26 +00:00
renovate[bot]
7f5615d109 chore(deps): update asf-ui digest to 220a8ec 2025-06-25 18:05:14 +00:00
renovate[bot]
43c76dc1af chore(deps): update crowdin/github-action action to v2.8.0 2025-06-25 07:11:07 +00:00
renovate[bot]
89deff7e06 chore(deps): update dependency markdig.signed to 0.41.3 2025-06-24 11:03:02 +00:00
ArchiBot
d536f7f56c Automatic translations update 2025-06-24 02:29:43 +00:00
renovate[bot]
8f43575a19 chore(deps): pin dependencies (#3439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-22 17:46:37 +02:00
Łukasz Domeradzki
53adc67be1 Misc 2025-06-22 17:46:18 +02:00
Łukasz Domeradzki
e4addcadc8 Update renovate.json5 2025-06-22 16:23:21 +02:00
Łukasz Domeradzki
c48c1e6acd Kill build environment
It causes only unnecessary spam in PRs, the other environments still make sense
2025-06-22 15:47:17 +02:00
ArchiBot
cd2baa25c8 Automatic translations update 2025-06-22 02:35:38 +00:00
renovate[bot]
93e19d0f82 chore(deps): update dependency scalar.aspnetcore to 2.5.0 2025-06-21 17:53:08 +00:00
renovate[bot]
a8527dee70 chore(deps): update asf-ui digest to 622b78e 2025-06-21 10:10:38 +00:00
ArchiBot
b209be1618 Automatic translations update 2025-06-21 02:26:29 +00:00
renovate[bot]
fa55d9f402 chore(deps): update wiki digest to fb78823 2025-06-20 19:56:55 +00:00
renovate[bot]
c1aeb0b0a1 chore(deps): update dependency scalar.aspnetcore to 2.4.22 2025-06-19 22:56:09 +00:00
Łukasz Domeradzki
6e2bd99600 Bump 2025-06-20 00:55:39 +02:00
Jack Nolddor
a99c1f93e1 chore: blacklist summer sale 2025 appid (#3437) 2025-06-20 00:54:11 +02:00
ArchiBot
abc2e2be69 Automatic translations update 2025-06-19 02:28:53 +00:00
renovate[bot]
7aff1f857b chore(deps): update docker/setup-buildx-action action to v3.11.1 2025-06-18 11:50:02 +00:00
renovate[bot]
e47a94aeaa chore(deps): update dependency scalar.aspnetcore to 2.4.19 2025-06-18 02:34:15 +00:00
renovate[bot]
12730e6cb5 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.12.1 2025-06-17 23:46:43 +00:00
renovate[bot]
9909a211a4 chore(deps): update dependency mstest to 3.9.3 2025-06-17 16:53:55 +00:00
renovate[bot]
32591514de chore(deps): update dependency system.linq.async to 6.0.3 2025-06-17 03:14:00 +00:00
ArchiBot
05f65e0d9d Automatic translations update 2025-06-17 02:29:31 +00:00
renovate[bot]
353b032efd chore(deps): update dependency scalar.aspnetcore to 2.4.17 2025-06-16 20:08:55 +00:00
renovate[bot]
fb16a9e50c chore(deps): update docker/setup-buildx-action action to v3.11.0 2025-06-16 16:08:38 +00:00
ArchiBot
32165af41d Automatic translations update 2025-06-15 02:35:26 +00:00
renovate[bot]
3860d8d2fc chore(deps): update asf-ui digest to 6a9af77 2025-06-14 19:20:54 +00:00
Łukasz Domeradzki
dd6a8cdb80 Bump 2025-06-14 21:19:36 +02:00
Łukasz Domeradzki
27562e52ef Respect LastAnnouncement fully in public listing
I don't recall why we needed that ShouldSendHeartBeats condition here before, it causes the routine to run always if the bot is currently not listed, which is unwanted e.g. if the server tells user to go away, or due to any other reason.
2025-06-14 21:10:00 +02:00
renovate[bot]
693c9d67dc chore(deps): update dependency scalar.aspnetcore to 2.4.16 2025-06-14 09:50:47 +00:00
renovate[bot]
71cd68d38f chore(deps): update asf-ui digest to ecaf0d8 2025-06-14 06:15:43 +00:00
ArchiBot
624283cee7 Automatic translations update 2025-06-14 02:26:04 +00:00
renovate[bot]
0bf9d2f040 chore(deps): update dependency scalar.aspnetcore to 2.4.15 2025-06-13 21:38:40 +00:00
renovate[bot]
c01d893b6b chore(deps): update dependency scalar.aspnetcore to 2.4.14 2025-06-13 15:54:48 +00:00
renovate[bot]
3b9d5c7ab6 chore(deps): update wiki digest to 4dd4924 2025-06-13 12:00:30 +00:00
Łukasz Domeradzki
05d5d90e3e Bump 2025-06-13 13:59:41 +02:00
Łukasz Domeradzki
53b84a9271 Closes #3436 2025-06-13 13:41:36 +02:00
Łukasz Domeradzki
44688a4ce8 Misc 2025-06-13 09:44:18 +02:00
ArchiBot
908f7f5ccb Automatic translations update 2025-06-13 02:29:04 +00:00
Łukasz Domeradzki
79c3ce14e8 Misc 2025-06-12 23:33:47 +02:00
ArchiBot
d8d0b1deb8 Automatic translations update 2025-06-12 02:28:23 +00:00
renovate[bot]
22b9f92663 chore(deps): update actions/attest-build-provenance action to v2.4.0 2025-06-11 22:41:25 +00:00
renovate[bot]
0900480e62 chore(deps): update wiki digest to 6a30b79 2025-06-11 19:44:23 +00:00
renovate[bot]
6b41f91543 chore(deps): update dotnet monorepo to 9.0.6 2025-06-11 02:39:44 +00:00
ArchiBot
708de736e0 Automatic translations update 2025-06-11 02:28:49 +00:00
renovate[bot]
816b23e277 chore(deps): update dependency mstest to 3.9.2 2025-06-10 17:29:17 +00:00
renovate[bot]
b1e472879f chore(deps): update asf-ui digest to ec7099f 2025-06-10 09:26:25 +00:00
Łukasz Domeradzki
714f734e58 Bump 2025-06-10 11:25:30 +02:00
Łukasz Domeradzki
4f279f1068 Misc 2025-06-10 11:19:20 +02:00
Łukasz Domeradzki
e2194894ce Reset temporary account credentials upon turning off bot for wrong ones 2025-06-10 11:07:37 +02:00
ArchiBot
6bfc7d5c7f Automatic translations update 2025-06-10 02:29:39 +00:00
Łukasz Domeradzki
0018da8f8e Bump 2025-06-09 21:38:58 +02:00
renovate[bot]
a4ddac5039 chore(deps): update dependency steamkit2 to 3.3.0 (#3435)
* chore(deps): update dependency steamkit2 to 3.3.0

* Adapt to SK2 breaking changes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>
2025-06-09 21:34:16 +02:00
ArchiBot
839bc06f88 Automatic translations update 2025-06-09 02:31:55 +00:00
Łukasz Domeradzki
2a1228c949 Bump 2025-06-09 00:23:01 +02:00
Łukasz Domeradzki
921d56a13d Bump 2025-06-09 00:15:16 +02:00
Łukasz Domeradzki
9fa1549e09 Closes #3434 2025-06-09 00:05:58 +02:00
ArchiBot
e0d19e256f Automatic translations update 2025-06-08 02:34:31 +00:00
renovate[bot]
18e8bf56e6 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.12.0 2025-06-07 18:52:25 +00:00
renovate[bot]
83500b7af5 chore(deps): update dependency scalar.aspnetcore to 2.4.13 2025-06-07 12:49:10 +00:00
renovate[bot]
548e8af27b chore(deps): update crowdin/github-action action to v2.7.1 2025-06-07 09:47:16 +00:00
renovate[bot]
54afced00a chore(deps): update asf-ui digest to 2526417 2025-06-07 05:05:49 +00:00
ArchiBot
b89967ecac Automatic translations update 2025-06-07 02:26:38 +00:00
renovate[bot]
130a25deaa chore(deps): update wiki digest to 5ef3932 2025-06-06 22:02:59 +00:00
ArchiBot
bc52dad3f5 Automatic translations update 2025-06-06 02:27:28 +00:00
renovate[bot]
4ce75e3ab0 chore(deps): update dependency markdig.signed to 0.41.2 2025-06-05 11:01:51 +00:00
renovate[bot]
a681b31630 chore(deps): update dependency scalar.aspnetcore to 2.4.8 2025-06-05 04:46:49 +00:00
ArchiBot
9777e87b0f Automatic translations update 2025-06-05 02:27:57 +00:00
renovate[bot]
8647201b1e chore(deps): update asf-ui digest to c0e3f38 2025-06-04 22:12:31 +00:00
Łukasz Domeradzki
543d3f2f85 Bump 2025-06-05 00:11:50 +02:00
Łukasz Domeradzki
0cd02d8de8 Misc chat ack improvements 2025-06-05 00:11:33 +02:00
ArchiBot
e93a486a85 Automatic translations update 2025-06-04 02:29:00 +00:00
ArchiBot
2225903719 Automatic translations update 2025-06-03 02:28:32 +00:00
ArchiBot
a854ba3ddf Automatic translations update 2025-06-02 02:30:53 +00:00
renovate[bot]
bededf4bec chore(deps): update asf-ui digest to e90f82b 2025-05-31 09:36:33 +00:00
ArchiBot
2fcb35e46c Automatic translations update 2025-05-31 02:24:32 +00:00
renovate[bot]
120509a02e chore(deps): update dependency scalar.aspnetcore to 2.4.7 2025-05-30 19:50:18 +00:00
renovate[bot]
6aca3ea0cc chore(deps): update dependency scalar.aspnetcore to 2.4.6 2025-05-30 11:50:30 +00:00
renovate[bot]
93ab09f6c5 chore(deps): update dependency scalar.aspnetcore to 2.4.5 2025-05-29 21:34:12 +00:00
Łukasz Domeradzki
c9fe2eb1d5 Bump 2025-05-29 23:33:36 +02:00
Łukasz Domeradzki
5d4666d538 Adapt to yet another Steam's breaking change 2025-05-29 23:29:40 +02:00
renovate[bot]
44549e2b2a chore(deps): update dependency nlog.web.aspnetcore to 5.5.0 (#3429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 21:38:13 +02:00
ArchiBot
b20f8adb5f Automatic translations update 2025-05-29 02:26:58 +00:00
renovate[bot]
0983ef309f chore(deps): update docker/build-push-action action to v6.18.0 2025-05-28 08:44:36 +00:00
renovate[bot]
8a24c037ed chore(deps): update dependency scalar.aspnetcore to 2.4.4 2025-05-28 03:29:38 +00:00
renovate[bot]
dad1d7fda9 chore(deps): update asf-ui digest to 9920764 2025-05-27 23:04:44 +00:00
renovate[bot]
cce7c1c7c8 chore(deps): update dependency mstest to 3.9.1 2025-05-27 16:45:59 +00:00
ArchiBot
ac209cfefb Automatic translations update 2025-05-27 02:25:49 +00:00
Łukasz Domeradzki
62e9058966 Bump 2025-05-25 19:21:24 +02:00
Łukasz Domeradzki
21a202e47d Misc 2025-05-25 19:18:36 +02:00
Łukasz Domeradzki
274c63c166 Log less info about failing request by default
Some URLs can expose potentially-sensitive details otherwise, such as GetTradeOffers
2025-05-25 19:15:58 +02:00
ArchiBot
207d10c2be Automatic translations update 2025-05-25 02:32:04 +00:00
Łukasz Domeradzki
9dc220e0d5 Bump 2025-05-24 21:50:14 +02:00
Łukasz Domeradzki
73f0cf23f8 Add workaround for 429 in trade offers 2025-05-24 21:49:45 +02:00
Łukasz Domeradzki
0b24380b2e Improve logging of api calls 2025-05-24 18:51:51 +02:00
Łukasz Domeradzki
ff485845bf Log less info about failing request by default
Some URLs can expose potentially-sensitive details otherwise, such as GetTradeOffers
2025-05-24 18:33:23 +02:00
ArchiBot
198e408ba4 Automatic translations update 2025-05-24 02:23:52 +00:00
renovate[bot]
2f5f4661c2 chore(deps): update dependency scalar.aspnetcore to 2.4.3 2025-05-23 22:45:17 +00:00
renovate[bot]
430dad82db chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.11.0 2025-05-23 18:27:09 +00:00
renovate[bot]
9fce02c2ef chore(deps): update dependency scalar.aspnetcore to 2.4.2 2025-05-23 02:53:14 +00:00
ArchiBot
9d2d7e33e3 Automatic translations update 2025-05-23 02:26:27 +00:00
renovate[bot]
fc390e0b4c chore(deps): update wiki digest to 9d8da2b 2025-05-22 16:52:03 +00:00
Łukasz Domeradzki
2d3fbf3080 Bump 2025-05-22 13:49:52 +02:00
Łukasz Domeradzki
b7152bfb0a Closes #3428 2025-05-22 13:46:19 +02:00
renovate[bot]
b3f216d25d chore(deps): update dependency scalar.aspnetcore to 2.4.1 2025-05-22 07:51:36 +00:00
ArchiBot
1caa12c7e8 Automatic translations update 2025-05-22 02:25:48 +00:00
renovate[bot]
94e3a9a5f8 chore(deps): update asf-ui digest to 30223b6 2025-05-20 23:32:22 +00:00
renovate[bot]
9cf20708e6 chore(deps): update dependency mstest to 3.9.0 2025-05-20 12:52:58 +00:00
ArchiBot
c60b7b0252 Automatic translations update 2025-05-20 02:26:56 +00:00
ArchiBot
3477bcefc3 Automatic translations update 2025-05-19 02:29:36 +00:00
ArchiBot
549a76ee7e Automatic translations update 2025-05-18 02:30:19 +00:00
renovate[bot]
6c32cebe83 chore(deps): update wiki digest to 2cd6a9f 2025-05-17 18:31:15 +00:00
renovate[bot]
d30d4d202a chore(deps): update asf-ui digest to 9eee3d3 2025-05-16 11:19:02 +00:00
renovate[bot]
d545622d0c chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.10.0 2025-05-16 02:44:52 +00:00
renovate[bot]
f33016d523 chore(deps): update dependency scalar.aspnetcore to 2.3.1 2025-05-15 23:53:22 +00:00
renovate[bot]
8a531262e7 chore(deps): update docker/build-push-action action to v6.17.0 2025-05-15 18:52:16 +00:00
ArchiBot
cf0dd510c2 Automatic translations update 2025-05-15 02:24:28 +00:00
renovate[bot]
d735d1bda8 chore(deps): update dotnet monorepo 2025-05-14 02:54:23 +00:00
ArchiBot
e4dc83fc86 Automatic translations update 2025-05-14 02:25:22 +00:00
renovate[bot]
13263a8d1e chore(deps): update asf-ui digest to 0b5af7a 2025-05-13 07:05:57 +00:00
renovate[bot]
3e06b5f8c8 chore(deps): update dependency scalar.aspnetcore to 2.3.0 2025-05-13 03:08:45 +00:00
ArchiBot
857fc6a3c6 Automatic translations update 2025-05-13 02:26:39 +00:00
Łukasz Domeradzki
ad461e5b8c Misc 2025-05-12 21:51:06 +02:00
Łukasz Domeradzki
5464ef4353 Misc 2025-05-12 21:35:13 +02:00
Łukasz Domeradzki
e8f4737e81 Misc 2025-05-12 21:31:30 +02:00
Łukasz Domeradzki
1dcaf98774 Always update app change numbers 2025-05-12 10:14:54 +02:00
ArchiBot
e081267a9b Automatic translations update 2025-05-12 02:27:47 +00:00
Łukasz Domeradzki
6ec7f4609a Even further misc 2025-05-11 22:25:15 +02:00
Łukasz Domeradzki
0bef5ccfa9 Misc
Edge case if previously-store-only package suddenly pops up in license list (normally would fix itself only after 7 days)
2025-05-11 22:21:43 +02:00
Łukasz Domeradzki
512545b657 Bump 2025-05-11 21:45:16 +02:00
Łukasz Domeradzki
390d9ac57c Use LINQ chunks in STD 2025-05-11 21:42:00 +02:00
Łukasz Domeradzki
10abfb847f Closes #3415 (#3427)
* Closes #3415

* Misc

* Refresh tokens always for non-listed packages
2025-05-11 21:36:16 +02:00
renovate[bot]
a19611c3ae chore(deps): update asf-ui digest to f2af2b8 2025-05-11 06:46:09 +00:00
ArchiBot
44840aa0ff Automatic translations update 2025-05-11 02:28:49 +00:00
ArchiBot
f0945efd3a Automatic translations update 2025-05-10 02:23:04 +00:00
Łukasz Domeradzki
cba29bb2c5 Resolve misc TODOs 2025-05-09 23:31:27 +02:00
ArchiBot
08c9636cd9 Automatic translations update 2025-05-09 02:24:46 +00:00
renovate[bot]
d940c4ac82 chore(deps): update asf-ui digest to 1205ace (#3425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 12:41:38 +02:00
ArchiBot
208e844c97 Automatic translations update 2025-05-08 02:25:45 +00:00
renovate[bot]
9f808a42a3 chore(deps): update dependency humanizer to 3.0.0-beta.96 2025-05-07 21:58:53 +00:00
Łukasz Domeradzki
cf9a578815 Misc tests improvement 2025-05-07 23:58:11 +02:00
Łukasz Domeradzki
449f3556a7 Add extra unit test 2025-05-07 23:49:08 +02:00
Łukasz Domeradzki
a3736d6cd6 Misc optimization 2025-05-07 23:21:04 +02:00
Łukasz Domeradzki
f975af721e Misc 2025-05-07 23:18:41 +02:00
Łukasz Domeradzki
e23dfe7846 Be more strict with plugin asset names 2025-05-07 18:55:32 +02:00
renovate[bot]
255a72e2ae chore(deps): update asf-ui digest to afc4c5a 2025-05-07 03:42:36 +00:00
renovate[bot]
de3aa36b1f chore(deps): update opentelemetry-dotnet-contrib monorepo to 1.12.0 2025-05-05 19:33:15 +00:00
renovate[bot]
ea844f6501 chore(deps): update dependency scalar.aspnetcore to 2.2.7 2025-05-05 15:42:53 +00:00
ArchiBot
9bbdc5f8d9 Automatic translations update 2025-05-05 02:26:45 +00:00
Łukasz Domeradzki
0644e634e7 Bump 2025-05-05 00:49:37 +02:00
Łukasz Domeradzki
66774631e7 Closes #3424 2025-05-05 00:48:48 +02:00
ArchiBot
735982ce4e Automatic translations update 2025-05-03 02:23:17 +00:00
Łukasz Domeradzki
0c207b4e2f Make NowFarming property public 2025-05-02 23:49:34 +02:00
Łukasz Domeradzki
cf5d7fd192 Misc 2025-05-02 13:23:59 +02:00
Łukasz Domeradzki
a19aadd826 Bump 2025-05-02 12:59:21 +02:00
Łukasz Domeradzki
f62da0e273 Closes #3420 2025-05-02 12:56:54 +02:00
Łukasz Domeradzki
af6f9466a8 Misc 2025-05-02 09:27:24 +02:00
ArchiBot
2ee53d8318 Automatic translations update 2025-05-02 02:23:48 +00:00
Łukasz Domeradzki
b6a5989770 Misc 2025-05-01 23:54:03 +02:00
Łukasz Domeradzki
193811cb9b Add support for Japanese Steam client language 2025-05-01 23:50:07 +02:00
Łukasz Domeradzki
0c23177455 Bump 2025-05-01 21:23:05 +02:00
Łukasz Domeradzki
c525ca5642 Bump 2025-05-01 21:22:33 +02:00
renovate[bot]
e4a726672d chore(deps): update dependency scalar.aspnetcore to 2.2.5 2025-05-01 14:53:32 +00:00
renovate[bot]
a897615d0e chore(deps): update asf-ui digest to af94f76 2025-05-01 09:09:15 +00:00
Łukasz Domeradzki
3e5ffe10b7 Misc further improvements to cross-process semaphores 2025-05-01 10:14:23 +02:00
ArchiBot
13131c769c Automatic translations update 2025-05-01 02:29:48 +00:00
renovate[bot]
449050b6df chore(deps): update dependency opentelemetry.extensions.hosting to 1.12.0 2025-04-30 08:35:30 +00:00
renovate[bot]
01b5251a39 chore(deps): update dependency scalar.aspnetcore to 2.2.2 2025-04-30 04:02:17 +00:00
Łukasz Domeradzki
6b6c976061 Modernize access modes upon creation
We can make use of some newly introduced overloads, not only improving on performance but also fixing potential (impossible to achieve in practice) crash between creation and mode setting
2025-04-29 18:34:25 +02:00
Łukasz Domeradzki
eeed61a9f6 Avoid some extra overhead if www folder is not found 2025-04-29 16:24:22 +02:00
Łukasz Domeradzki
4afe7654af Misc
The execute permission is unnecessary on temporary files, only directories need it
2025-04-29 15:37:30 +02:00
renovate[bot]
acdd504a7c chore(deps): update actions/attest-build-provenance action to v2.3.0 2025-04-28 16:58:21 +00:00
renovate[bot]
65c9477bb9 chore(deps): update dependency markdig.signed to 0.41.1 2025-04-28 11:50:58 +00:00
ArchiBot
eaa11c76ab Automatic translations update 2025-04-27 02:26:09 +00:00
ArchiBot
2ba0eb77ad Automatic translations update 2025-04-26 02:22:25 +00:00
renovate[bot]
3761e07c6d chore(deps): update asf-ui digest to 7c49cd8 2025-04-25 23:31:21 +00:00
renovate[bot]
c787c8ece7 chore(deps): update docker/build-push-action action to v6.16.0 2025-04-25 19:27:04 +00:00
renovate[bot]
f8e8e2c8a4 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.9.0 2025-04-25 13:54:23 +00:00
renovate[bot]
3960ec16b4 chore(deps): update asf-ui digest to 5165da4 2025-04-25 07:26:01 +00:00
Łukasz Domeradzki
ddb04b97a8 Bump 2025-04-25 09:25:24 +02:00
ArchiBot
868d593c5c Automatic translations update 2025-04-25 02:23:51 +00:00
renovate[bot]
e6a5524cbc chore(deps): update actions/download-artifact action to v4.3.0 2025-04-24 16:54:03 +00:00
renovate[bot]
cf179878ae chore(deps): update dependency scalar.aspnetcore to 2.2.1 2025-04-24 02:53:43 +00:00
ArchiBot
394f10f384 Automatic translations update 2025-04-24 02:23:45 +00:00
renovate[bot]
df814b1acb chore(deps): update wiki digest to cb76466 2025-04-23 16:44:22 +00:00
renovate[bot]
b7439693fc chore(deps): update dependency scalar.aspnetcore to 2.1.18 2025-04-22 19:25:42 +00:00
renovate[bot]
c009fc8dc8 chore(deps): update asf-ui digest to 6f0393c 2025-04-22 13:10:44 +00:00
renovate[bot]
cf62c0802f chore(deps): update dependency steamkit2 to 3.1.0 2025-04-21 15:29:13 +00:00
renovate[bot]
5f19372429 chore(deps): update dependency anglesharp to 1.3.0 2025-04-19 01:52:51 +00:00
renovate[bot]
adf2eccc9c chore(deps): update dependency scalar.aspnetcore to 2.1.17 2025-04-18 23:31:32 +00:00
renovate[bot]
ce463bf780 chore(deps): update asf-ui digest to 992890b 2025-04-18 18:58:10 +00:00
renovate[bot]
31fe442476 chore(deps): update dependency scalar.aspnetcore to 2.1.16 2025-04-17 01:36:25 +00:00
renovate[bot]
45daa46cf9 chore(deps): update dependency scalar.aspnetcore to 2.1.15 2025-04-16 19:57:00 +00:00
ArchiBot
83c12f5636 Automatic translations update 2025-04-16 02:23:33 +00:00
renovate[bot]
1683729772 chore(deps): update dependency markdig.signed to 0.41.0 2025-04-15 11:44:30 +00:00
renovate[bot]
21cd3d8dbb chore(deps): update asf-ui digest to c4ddfac 2025-04-14 11:09:57 +00:00
renovate[bot]
4b34905358 chore(deps): update actions/setup-node action to v4.4.0 2025-04-14 07:30:15 +00:00
Łukasz Domeradzki
c0214f16fc Bump 2025-04-14 09:29:37 +02:00
Łukasz Domeradzki
458cb95422 Bump 2025-04-14 09:29:09 +02:00
Łukasz Domeradzki
ef844c168c Bump 2025-04-14 09:18:23 +02:00
Łukasz Domeradzki
96e5924c0c Misc bulletproofing 2025-04-14 09:17:52 +02:00
Outzzz
d20fa79897 get inventory with language (#3409)
* get inventory with language

* get inventory with language

* edit

* edit
2025-04-14 09:15:11 +02:00
renovate[bot]
f1a49cdff0 chore(deps): update dependency scalar.aspnetcore to 2.1.13 2025-04-11 22:01:40 +00:00
renovate[bot]
baa1339573 chore(deps): update dependency scalar.aspnetcore to 2.1.11 2025-04-11 02:24:16 +00:00
renovate[bot]
53802829b6 chore(deps): update dependency scalar.aspnetcore to 2.1.10 2025-04-10 18:53:30 +00:00
renovate[bot]
ee7866590f chore(deps): update dotnet monorepo to 9.0.4 2025-04-10 01:48:00 +00:00
renovate[bot]
94cb4e4e0a chore(deps): update dependency scalar.aspnetcore to 2.1.9 2025-04-09 22:33:44 +00:00
renovate[bot]
78e5ab2cbb chore(deps): update asf-ui digest to 6596a83 2025-04-09 16:37:01 +00:00
renovate[bot]
a52bf7774c chore(deps): update dependency scalar.aspnetcore to 2.1.8 2025-04-09 10:52:51 +00:00
renovate[bot]
4aed278f9c chore(deps): update dependency microsoft.codeanalysis.resxsourcegenerator to 3.11.0-beta1.25123.3 2025-04-09 07:37:20 +00:00
renovate[bot]
1631e1687b chore(deps): update asf-ui digest to e425aab 2025-04-09 03:19:38 +00:00
ArchiBot
4738171fd3 Automatic translations update 2025-04-09 02:23:30 +00:00
renovate[bot]
001eabe25d chore(deps): update asf-ui digest to fdcd3c3 2025-04-08 11:07:38 +00:00
renovate[bot]
3d535c4c72 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.8.0 2025-04-08 06:32:55 +00:00
renovate[bot]
f59a3ca233 chore(deps): update asf-ui digest to cda7dfa 2025-04-07 23:28:05 +00:00
renovate[bot]
d3134afc5a chore(deps): update dependency scalar.aspnetcore to 2.1.7 2025-04-07 20:02:29 +00:00
ArchiBot
39bf821f78 Automatic translations update 2025-04-07 02:23:52 +00:00
ArchiBot
5324dad0c6 Automatic translations update 2025-04-06 02:24:03 +00:00
renovate[bot]
ab92bbf3d3 chore(deps): update dependency scalar.aspnetcore to 2.1.6 2025-04-04 19:35:03 +00:00
renovate[bot]
63a481629e chore(deps): update asf-ui digest to e9c6a29 2025-04-04 06:36:58 +00:00
renovate[bot]
d44d075b20 chore(deps): update peter-evans/dockerhub-description action to v4.0.2 2025-04-03 16:06:11 +00:00
renovate[bot]
030d71c52a chore(deps): update asf-ui digest to a69ee58 2025-04-03 10:06:29 +00:00
renovate[bot]
2fe0a2635e chore(deps): update dependency scalar.aspnetcore to 2.1.5 2025-04-03 02:41:38 +00:00
ArchiBot
ae45f474e3 Automatic translations update 2025-04-03 02:22:55 +00:00
renovate[bot]
33e98a995e chore(deps): update crowdin/github-action action to v2.7.0 (#3404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 09:21:24 +02:00
renovate[bot]
92b0e4bfb6 chore(deps): update asf-ui digest to 2771a20 (#3406)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 05:30:59 +00:00
ArchiBot
2403ac17dc Automatic translations update 2025-04-02 02:23:43 +00:00
ArchiBot
ca74047828 Automatic translations update 2025-04-01 08:23:24 +00:00
Łukasz Domeradzki
21e951092d Update translations.yml 2025-04-01 10:22:47 +02:00
renovate[bot]
3ba97b49ba chore(deps): update peter-evans/dockerhub-description action to v4.0.1 2025-04-01 03:16:02 +00:00
renovate[bot]
5e739e483e chore(deps): update dependency scalar.aspnetcore to 2.1.4 2025-03-31 23:41:00 +00:00
renovate[bot]
992a4563c2 chore(deps): update asf-ui digest to b0cf3f0 2025-03-31 18:26:47 +00:00
renovate[bot]
3e9e1cb6ed chore(deps): update wiki digest to 55469ee 2025-03-31 12:35:58 +00:00
Łukasz Domeradzki
61f876480a General code cleanups 2025-03-31 00:06:05 +02:00
Łukasz Domeradzki
c2b1d1356c Misc optimization 2025-03-30 21:12:24 +02:00
Łukasz Domeradzki
d5ac569a6a Bump 2025-03-30 21:06:57 +02:00
Łukasz Domeradzki
0b1ddd39d5 Optimize serializable file writes 2025-03-30 21:06:33 +02:00
renovate[bot]
cbe0502154 chore(deps): update asf-ui digest to c311378 2025-03-30 06:14:12 +00:00
renovate[bot]
816914dd10 chore(deps): update crazy-max/ghaction-import-gpg action to v6.3.0 2025-03-30 02:09:52 +00:00
renovate[bot]
bcb4320f5d chore(deps): update dependency scalar.aspnetcore to 2.1.3 2025-03-28 22:48:05 +00:00
renovate[bot]
bc4d5d37ff chore(deps): update asf-ui digest to c16acf5 2025-03-27 12:04:46 +00:00
renovate[bot]
1996763751 chore(deps): update dependency scalar.aspnetcore to 2.1.2 2025-03-26 18:53:18 +00:00
renovate[bot]
99a0a5cc48 chore(deps): update asf-ui digest to a93167e 2025-03-26 05:57:09 +00:00
ArchiBot
63c91d2d19 Automatic translations update 2025-03-26 02:22:32 +00:00
renovate[bot]
d6be6cf435 chore(deps): update asf-ui digest to ee55958 2025-03-25 03:00:04 +00:00
renovate[bot]
8f7f9a27ee chore(deps): update asf-ui digest to 0b9c0c2 2025-03-22 06:46:28 +00:00
ArchiBot
b776d4d882 Automatic translations update 2025-03-22 02:21:23 +00:00
renovate[bot]
63c81c2403 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.7.0 2025-03-21 19:49:13 +00:00
renovate[bot]
ad9a77ff19 chore(deps): update dependency scalar.aspnetcore to 2.1.1 2025-03-20 19:28:04 +00:00
renovate[bot]
2e17a15d60 chore(deps): update asf-ui digest to 5dcfb93 2025-03-20 03:09:58 +00:00
renovate[bot]
3fae20673b chore(deps): update actions/upload-artifact action to v4.6.2 2025-03-19 22:42:10 +00:00
renovate[bot]
5b36c1c286 chore(deps): update actions/download-artifact action to v4.2.1 2025-03-19 17:14:20 +00:00
renovate[bot]
4fc4d011f2 chore(deps): update dependency scalar.aspnetcore to 2.1.0 2025-03-19 07:40:28 +00:00
ArchiBot
91aa9754bb Automatic translations update 2025-03-19 02:22:16 +00:00
renovate[bot]
a6ba407ebf chore(deps): update actions/download-artifact action to v4.2.0 2025-03-18 18:54:59 +00:00
Łukasz Domeradzki
1cdb128c6b Bump 2025-03-18 19:54:28 +01:00
Łukasz Domeradzki
38ca6b7642 Update Scalar.AspNetCore 2025-03-18 19:50:45 +01:00
Łukasz Domeradzki
65c7a60c92 Switch to scalar for swagger-ui generation (#3391) 2025-03-18 19:47:43 +01:00
renovate[bot]
77c802ee5f chore(deps): update dependency swashbuckle.aspnetcore.swaggerui to 7.3.2 2025-03-18 11:31:12 +00:00
renovate[bot]
a8f23159f4 chore(deps): update asf-ui digest to d847829 2025-03-18 03:09:59 +00:00
renovate[bot]
ee8a7a6f61 chore(deps): update actions/setup-node action to v4.3.0 2025-03-17 23:28:51 +00:00
renovate[bot]
4a612b9436 chore(deps): update dependency mstest to 3.8.3 2025-03-17 16:55:26 +00:00
renovate[bot]
4d89995016 chore(deps): update asf-ui digest to f2393a2 2025-03-17 12:10:41 +00:00
renovate[bot]
368c453504 chore(deps): update actions/setup-dotnet action to v4.3.1 2025-03-17 05:46:02 +00:00
ArchiBot
1694851395 Automatic translations update 2025-03-16 02:23:25 +00:00
renovate[bot]
1ed4f3f456 chore(deps): update docker/login-action action to v3.4.0 2025-03-14 11:57:11 +00:00
renovate[bot]
588d49eeb9 chore(deps): update asf-ui digest to 41bfaf0 2025-03-12 19:26:11 +00:00
renovate[bot]
22d4f22da6 chore(deps): update asf-ui digest to 2c3829f (#3396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 11:39:52 +00:00
renovate[bot]
ced640f024 chore(deps): update dotnet monorepo to 9.0.3 2025-03-11 21:54:46 +00:00
renovate[bot]
7b8804b53c chore(deps): update dependency microsoft.codeanalysis.resxsourcegenerator to 3.11.0-beta1.25076.3 2025-03-11 19:08:49 +00:00
renovate[bot]
5d25742faf chore(deps): update asf-ui digest to bc98426 2025-03-11 03:22:59 +00:00
ArchiBot
4460b18c5e Automatic translations update 2025-03-11 02:21:55 +00:00
ArchiBot
cb5eb499d9 Automatic translations update 2025-03-10 02:11:40 +00:00
Łukasz Domeradzki
9382c6d390 Bump 2025-03-09 23:51:11 +01:00
Łukasz Domeradzki
7cd091cfa2 Implement dummy answer to ClientGetClientAppList request 2025-03-09 23:47:10 +01:00
renovate[bot]
961dc4cf5d chore(deps): update asf-ui digest to c1e8e5b 2025-03-09 05:32:53 +00:00
ArchiBot
f77193dc48 Automatic translations update 2025-03-09 02:10:59 +00:00
renovate[bot]
a4b31d2cdc chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.6.1 (#3393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 21:50:56 +00:00
renovate[bot]
83b3edae86 chore(deps): update asf-ui digest to a48983b (#3394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 17:55:33 +00:00
Łukasz Domeradzki
ebc4601ed4 Add another useful json helper 2025-03-08 15:00:38 +01:00
Łukasz Domeradzki
a478c02967 Remove qodana support 2025-03-08 11:56:56 +01:00
renovate[bot]
aac1f81704 chore(deps): update opentelemetry-dotnet-contrib monorepo to 1.11.1 2025-03-06 05:37:39 +00:00
ArchiBot
100b85abe0 Automatic translations update 2025-03-06 02:21:28 +00:00
renovate[bot]
bb52122db1 chore(deps): update actions/attest-build-provenance action to v2.2.3 2025-03-05 23:37:03 +00:00
renovate[bot]
397b36c557 chore(deps): update asf-ui digest to 4cba2a6 2025-03-05 12:14:04 +00:00
renovate[bot]
77b041ea67 chore(deps): update dependency opentelemetry.extensions.hosting to 1.11.2 2025-03-04 22:34:48 +00:00
renovate[bot]
b1d896ae70 chore(deps): update asf-ui digest to c8faaa2 2025-03-04 03:28:37 +00:00
ArchiBot
8f5157d8dd Automatic translations update 2025-03-03 08:39:18 +00:00
Łukasz Domeradzki
c5cf5f70c8 Bump 2025-03-02 22:17:40 +01:00
ArchiBot
88f7856c9b Automatic translations update 2025-03-02 11:26:14 +00:00
Łukasz Domeradzki
d843d1d5b1 Remove deprecated functionalities, bump 2025-03-01 15:22:04 +01:00
renovate[bot]
921b416374 chore(deps): update asf-ui digest to e0efa0f 2025-02-28 22:08:34 +00:00
renovate[bot]
c2eac39145 chore(deps): update dependency swashbuckle.aspnetcore.swaggerui to 7.3.1 2025-02-27 23:10:42 +00:00
renovate[bot]
467dbf723d chore(deps): update actions/attest-build-provenance action to v2.2.2 2025-02-27 19:21:44 +00:00
renovate[bot]
9460b476dd chore(deps): update docker/setup-buildx-action action to v3.10.0 2025-02-27 06:38:29 +00:00
renovate[bot]
16fb5a7d98 chore(deps): update actions/attest-build-provenance action to v2.2.1 2025-02-27 01:35:23 +00:00
renovate[bot]
c17da5951d chore(deps): update asf-ui digest to 19776b3 2025-02-26 22:25:40 +00:00
renovate[bot]
66a959d21c chore(deps): update docker/build-push-action action to v6.15.0 2025-02-26 16:27:29 +00:00
renovate[bot]
480037b84b chore(deps): update dependency swashbuckle.aspnetcore.swaggerui to 7.3.0 2025-02-26 11:52:42 +00:00
renovate[bot]
fa7867ab18 chore(deps): update actions/download-artifact action to v4.1.9 2025-02-26 02:52:48 +00:00
ArchiBot
0c5cdfae0a Automatic translations update 2025-02-26 02:20:45 +00:00
renovate[bot]
7e5f82d65b chore(deps): update asf-ui digest to 9546310 2025-02-25 07:33:09 +00:00
Łukasz Domeradzki
9753b36769 Misc 2025-02-23 19:56:23 +01:00
Łukasz Domeradzki
fd2c1e8c24 Bump 2025-02-23 19:21:50 +01:00
Łukasz Domeradzki
e5c9defac8 Closes #3184 2025-02-23 19:13:44 +01:00
Łukasz Domeradzki
33c8c0f0d6 Rewrite GitHub service from xpath to css selectors 2025-02-23 17:28:36 +01:00
renovate[bot]
197b41d96f chore(deps): update ncipollo/release-action action to v1.16.0 2025-02-22 17:50:41 +00:00
renovate[bot]
52a8bcbbfe chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.6.0 2025-02-22 05:46:28 +00:00
renovate[bot]
e102b6c565 chore(deps): update asf-ui digest to 687959a 2025-02-22 02:49:10 +00:00
renovate[bot]
f899508253 chore(deps): update actions/upload-artifact action to v4.6.1 2025-02-21 23:35:02 +00:00
renovate[bot]
ab20d5fccf chore(deps): update github/codeql-action action to v3.28.10 2025-02-21 19:00:23 +00:00
ArchiBot
3c9827b1e1 Automatic translations update 2025-02-21 02:19:55 +00:00
renovate[bot]
9636fa1da1 chore(deps): update docker/build-push-action action to v6.14.0 2025-02-19 18:45:04 +00:00
renovate[bot]
b5ad577821 chore(deps): update dependency mstest to 3.8.2 2025-02-19 15:02:36 +00:00
renovate[bot]
ca483a91f3 chore(deps): update dependency mstest to 3.8.1 2025-02-18 21:39:39 +00:00
renovate[bot]
49e904c25a chore(deps): update asf-ui digest to b1b2d2e 2025-02-18 19:24:37 +00:00
renovate[bot]
9a0c0bfff5 chore(deps): update crowdin/github-action action to v2.6.1 2025-02-18 12:03:05 +00:00
ArchiBot
2ed1214d37 Automatic translations update 2025-02-18 02:19:06 +00:00
ArchiBot
adf417e2f1 Automatic translations update 2025-02-17 02:20:41 +00:00
Łukasz Domeradzki
ab333dc0b3 Bump 2025-02-16 17:44:42 +01:00
Łukasz Domeradzki
3f079a8fea Add new ASF API endpoint for inventory summary, add inventory command
Wow, new features in ASF?!
2025-02-16 17:26:51 +01:00
renovate[bot]
90db25e4de chore(deps): update asf-ui digest to 6908495 2025-02-16 06:46:41 +00:00
ArchiBot
ec793e22a1 Automatic translations update 2025-02-16 02:21:22 +00:00
renovate[bot]
c76f6caa2b chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.5.0 2025-02-15 01:25:06 +00:00
renovate[bot]
60245d5399 chore(deps): update asf-ui digest to 9b20a8f 2025-02-14 19:41:41 +00:00
renovate[bot]
8fb2ec61b4 chore(deps): update asf-ui digest to 41e8159 2025-02-14 11:02:36 +00:00
renovate[bot]
4a939ad607 chore(deps): update asf-ui digest to 6e74737 2025-02-14 02:59:21 +00:00
renovate[bot]
ba1be3df10 chore(deps): update asf-ui digest to 88995f5 2025-02-13 03:25:18 +00:00
ArchiBot
93333f0ece Automatic translations update 2025-02-13 02:19:24 +00:00
Łukasz Domeradzki
dcde5d9d54 Misc test improvements 2025-02-13 00:20:10 +01:00
renovate[bot]
52af5c85f0 chore(deps): update dependency mstest to 3.8.0 2025-02-12 15:13:10 +00:00
Łukasz Domeradzki
d47f17c7ad Bump 2025-02-12 16:12:41 +01:00
Łukasz Domeradzki
8533659b74 Misc cleanup 2025-02-12 16:12:01 +01:00
Łukasz Domeradzki
2f658a3d4e Closes #3376 2025-02-12 16:11:10 +01:00
renovate[bot]
2c31b5f11f chore(deps): update dotnet monorepo to 9.0.2 2025-02-11 22:08:26 +00:00
renovate[bot]
c66bd6259c chore(deps): update dependency microsoft.codeanalysis.resxsourcegenerator to 3.11.0-beta1.24629.2 2025-02-11 20:08:15 +00:00
renovate[bot]
145c64389d chore(deps): update asf-ui digest to 1bf87dd 2025-02-11 16:12:08 +00:00
renovate[bot]
23263e634e chore(deps): update crowdin/github-action action to v2.6.0 2025-02-11 11:55:35 +00:00
renovate[bot]
2074865a7c chore(deps): update asf-ui digest to a5471a6 2025-02-11 05:32:13 +00:00
ArchiBot
4cb9f54204 Automatic translations update 2025-02-11 02:19:51 +00:00
renovate[bot]
78b1d7de0f chore(deps): update dependency anglesharp.xpath to 2.0.5 2025-02-10 21:36:42 +00:00
Łukasz Domeradzki
c296790226 Bump 2025-02-09 21:16:40 +01:00
Łukasz Domeradzki
d7fec15597 Bump 2025-02-09 21:16:25 +01:00
Łukasz Domeradzki
f33fda8313 Misc 2025-02-09 21:15:24 +01:00
Łukasz Domeradzki
62ce58e148 Closes #3378 2025-02-09 21:14:05 +01:00
renovate[bot]
db70633721 chore(deps): update asf-ui digest to 3b86172 2025-02-09 09:56:32 +00:00
ArchiBot
8c62b01d19 Automatic translations update 2025-02-08 02:17:22 +00:00
renovate[bot]
5f3b2c9a0d chore(deps): update github/codeql-action action to v3.28.9 2025-02-07 18:33:48 +00:00
Łukasz Domeradzki
1afac53318 Closes #3384 2025-02-07 15:31:11 +01:00
renovate[bot]
78d2ff1645 chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.4.0 2025-02-07 05:57:00 +00:00
renovate[bot]
31082e0184 chore(deps): update docker/setup-buildx-action action to v3.9.0 2025-02-06 17:30:45 +00:00
renovate[bot]
7e942d6481 chore(deps): update asf-ui digest to d006b23 (#3383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 13:43:03 +01:00
ArchiBot
af0659595e Automatic translations update 2025-02-06 02:19:05 +00:00
ArchiBot
89962bd393 Automatic translations update 2025-02-05 02:18:55 +00:00
Sebastian Göls
c71fe556a3 Fix typo (#3382) 2025-02-04 09:08:24 +01:00
ArchiBot
4c88e098c6 Automatic translations update 2025-02-04 02:18:24 +00:00
renovate[bot]
8069c19eef chore(deps): update wiki digest to 6603a8e 2025-02-03 21:46:23 +00:00
Łukasz Domeradzki
91fd6a1014 Bump 2025-02-03 11:15:30 +01:00
renovate[bot]
18be38352f chore(deps): update dependency steamkit2 to 3.0.2 2025-02-03 04:55:35 +00:00
renovate[bot]
e5029c983c chore(deps): update dependency nlog.web.aspnetcore to 5.4.0 2025-02-02 20:35:59 +00:00
renovate[bot]
e8bbaabc01 chore(deps): update asf-ui digest to fbdc518 2025-02-02 05:17:52 +00:00
ArchiBot
e62e3be68a Automatic translations update 2025-02-02 02:19:26 +00:00
ArchiBot
3c48bc1756 Automatic translations update 2025-01-31 02:18:21 +00:00
renovate[bot]
00a02f2be4 chore(deps): update actions/setup-dotnet action to v4.3.0 2025-01-30 12:42:26 +00:00
renovate[bot]
2c09757086 chore(deps): update asf-ui digest to 83b9d20 2025-01-30 04:21:04 +00:00
ArchiBot
73a4e8acd3 Automatic translations update 2025-01-30 02:17:28 +00:00
renovate[bot]
7b98fea247 chore(deps): update github/codeql-action action to v3.28.8 2025-01-29 20:39:05 +00:00
renovate[bot]
3cf25f6385 chore(deps): update github/codeql-action action to v3.28.7 2025-01-29 14:41:10 +00:00
ArchiBot
69d666b24d Automatic translations update 2025-01-29 02:17:55 +00:00
renovate[bot]
0a234166f6 chore(deps): update github/codeql-action action to v3.28.6 2025-01-28 05:56:43 +00:00
ArchiBot
68d63853f4 Automatic translations update 2025-01-28 02:18:08 +00:00
renovate[bot]
f3551594c3 chore(deps): update dependency mstest to 3.7.3 2025-01-27 21:11:56 +00:00
renovate[bot]
6effcc0d4f chore(deps): update asf-ui digest to 99a0294 2025-01-27 19:31:08 +00:00
Łukasz Domeradzki
1e3f535330 Bump 2025-01-27 20:30:37 +01:00
Łukasz Domeradzki
0a2ce30c82 Misc 2025-01-27 20:24:43 +01:00
Łukasz Domeradzki
dd37914703 Apply feedback 2025-01-27 20:12:35 +01:00
Łukasz Domeradzki
67c2e9c6d3 Record input from the user in the logs as well 2025-01-27 19:57:42 +01:00
Łukasz Domeradzki
ec151c383b Ensure rare console writes leave trace in the log as of what is happening 2025-01-27 19:44:47 +01:00
renovate[bot]
05079eb941 chore(deps): update opentelemetry-dotnet-contrib monorepo to 1.11.0 2025-01-27 09:50:46 +00:00
renovate[bot]
38ebfc9345 chore(deps): update actions/setup-node action to v4.2.0 2025-01-27 04:35:19 +00:00
renovate[bot]
fc4f4092f2 chore(deps): update docker/build-push-action action to v6.13.0 2025-01-26 17:30:19 +00:00
renovate[bot]
59f00714d6 chore(deps): update github/codeql-action action to v3.28.5 2025-01-26 13:06:44 +00:00
renovate[bot]
7d84599d0a chore(deps): update asf-ui digest to 907e0fb 2025-01-26 09:43:31 +00:00
renovate[bot]
da1e49ffde chore(deps): update crowdin/github-action action to v2.5.2 2025-01-24 08:25:11 +00:00
renovate[bot]
30498df864 chore(deps): update actions/attest-build-provenance action to v2.2.0 2025-01-24 04:30:18 +00:00
renovate[bot]
9ad08bd028 chore(deps): update github/codeql-action action to v3.28.4 2025-01-24 01:04:48 +00:00
renovate[bot]
70d190488f chore(deps): update dependency opentelemetry.extensions.hosting to 1.11.1 2025-01-23 23:16:35 +00:00
renovate[bot]
274136e3e1 chore(deps): update asf-ui digest to ad215c8 2025-01-22 05:58:53 +00:00
ArchiBot
9b81c390bf Automatic translations update 2025-01-22 02:19:11 +00:00
renovate[bot]
c649edf051 chore(deps): update github/codeql-action action to v3.28.2 2025-01-21 20:58:29 +00:00
renovate[bot]
7bb1528e78 chore(deps): update dependency mstest to 3.7.2 2025-01-21 18:58:00 +00:00
renovate[bot]
b8fb66c7fe chore(deps): update asf-ui digest to bbeba04 2025-01-21 05:56:07 +00:00
ArchiBot
075a234e14 Automatic translations update 2025-01-21 02:18:06 +00:00
Łukasz Domeradzki
7e6a79dac2 Misc rename 2025-01-20 12:32:09 +01:00
Łukasz Domeradzki
ff6473dbe6 Bump 2025-01-20 11:54:41 +01:00
Łukasz Domeradzki
ac01cca55e Go back to swagger in regards to #3376, add cmdline switch to force openapi 2025-01-20 11:49:35 +01:00
renovate[bot]
2a66fce878 chore(deps): update asf-ui digest to 3e24284 2025-01-19 05:05:53 +00:00
ArchiBot
9d5d6b792d Automatic translations update 2025-01-19 02:20:36 +00:00
renovate[bot]
7927ff859f chore(deps): update asf-ui digest to 9cd1203 2025-01-18 04:34:13 +00:00
renovate[bot]
f2992fa6dd chore(deps): update dependency microsoft.identitymodel.jsonwebtokens to 8.3.1 2025-01-18 00:24:41 +00:00
renovate[bot]
af27952ced chore(deps): update crowdin/github-action action to v2.5.1 2025-01-17 09:54:22 +00:00
ArchiBot
2f92a80bd6 Automatic translations update 2025-01-17 02:17:34 +00:00
renovate[bot]
786dc16bbd chore(deps): update dependency opentelemetry.extensions.hosting to 1.11.0 2025-01-16 13:11:57 +00:00
ArchiBot
947ed6234e Automatic translations update 2025-01-16 02:18:00 +00:00
renovate[bot]
6e8ee64918 chore(deps): update docker/build-push-action action to v6.12.0 2025-01-15 15:02:14 +00:00
renovate[bot]
ce0d423497 chore(deps): update dotnet monorepo to 9.0.1 2025-01-15 11:13:32 +00:00
renovate[bot]
851c8bdbf8 chore(deps): update dependency microsoft.codeanalysis.resxsourcegenerator to 3.11.0-beta1.24605.2 2025-01-15 05:05:22 +00:00
ArchiBot
9ed6586b93 Automatic translations update 2025-01-15 02:18:20 +00:00
renovate[bot]
fcb965e492 chore(deps): update asf-ui digest to e18599e 2025-01-14 20:55:47 +00:00
ArchiBot
32cde62815 Automatic translations update 2025-01-14 02:17:39 +00:00
renovate[bot]
d83e4fceca chore(deps): update dependency mstest to 3.7.1 2025-01-13 18:01:12 +00:00
ArchiBot
af1d017123 Automatic translations update 2025-01-13 02:20:56 +00:00
Sebastian Göls
3759e906cc Improve monitoring plugin (#3374) 2025-01-12 23:18:05 +01:00
renovate[bot]
764ec31559 chore(deps): update ncipollo/release-action action to v1.15.0 2025-01-12 05:12:51 +00:00
renovate[bot]
b3754fc32c chore(deps): update asf-ui digest to 9028c68 2025-01-11 06:42:38 +00:00
renovate[bot]
4780aa7601 chore(deps): update actions/upload-artifact action to v4.6.0 2025-01-11 01:24:26 +00:00
renovate[bot]
7c67a7409f chore(deps): update asf-ui digest to 345fe2b 2025-01-10 22:33:08 +00:00
renovate[bot]
db9969558f chore(deps): update github/codeql-action action to v3.28.1 2025-01-10 18:50:15 +00:00
renovate[bot]
78fd2ae333 chore(deps): update dependency markdig.signed to 0.40.0 2025-01-10 09:50:23 +00:00
renovate[bot]
6f4337c91e chore(deps): update docker/build-push-action action to v6.11.0 2025-01-08 14:52:37 +00:00
ArchiBot
a92430b4f0 Automatic translations update 2025-01-08 02:19:48 +00:00
Łukasz Domeradzki
f18a1d584d Misc monitoring stuff 2025-01-07 14:59:31 +01:00
Łukasz Domeradzki
1302fd8984 Misc monitoring plugin improvements 2025-01-07 14:38:29 +01:00
ArchiBot
289010ba88 Automatic translations update 2025-01-07 02:20:15 +00:00
ArchiBot
99f994b4b5 Automatic translations update 2025-01-06 02:21:19 +00:00
renovate[bot]
cd63f14f97 chore(deps): update wiki digest to 871cf91 2025-01-05 19:44:13 +00:00
Łukasz Domeradzki
b38748de18 Misc 2025-01-05 19:04:55 +01:00
Łukasz Domeradzki
730814f4f6 Use new field keyword 2025-01-05 16:51:43 +01:00
Łukasz Domeradzki
ccec47c8bd Resolve some qodana warnings 2025-01-05 16:22:54 +01:00
Łukasz Domeradzki
90ae809bbe Bump 2025-01-05 15:35:06 +01:00
164 changed files with 5140 additions and 2013 deletions

View File

@@ -1,7 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"config:best-practices",
":assignee(JustArchi)",
":automergeBranch",
":automergeDigest",
@@ -10,7 +11,17 @@
":disableRateLimiting",
":label(🤖 Automatic)"
],
"git-submodules": {
"enabled": true
}
},
"packageRules": [
{
// TODO: Allow updates of selected packages with no stable release (yet) to latest versions
"matchManagers": [ "nuget" ],
"matchPackageNames": [ "Microsoft.CodeAnalysis.ResxSourceGenerator", "OpenTelemetry.Exporter.Prometheus.AspNetCore" ],
"ignoreUnstable": false
}
]
}

View File

@@ -21,13 +21,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.2.0
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
@@ -38,4 +38,4 @@ jobs:
run: dotnet build -c "${{ matrix.configuration }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
- name: Run ${{ matrix.configuration }} ArchiSteamFarm.Tests
run: dotnet test ArchiSteamFarm.Tests -c "${{ matrix.configuration }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
run: dotnet test ArchiSteamFarm.Tests -c "${{ matrix.configuration }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --filter TestCategory!=Manual --nologo

View File

@@ -1,39 +0,0 @@
name: ASF-code-quality
on:
- push
env:
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
permissions:
checks: write
contents: write
pull-requests: write
security-events: write
jobs:
main:
environment: qa-qodana
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
with:
show-progress: false
- name: Run Qodana scan
uses: JetBrains/qodana-action@v2024.3.4
with:
args: --config,.github/qodana.yaml,--property=idea.headless.enable.statistics=false
pr-mode: false
upload-result: true
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
- name: Report Qodana results to GitHub
uses: github/codeql-action/upload-sarif@v3.28.0
with:
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json

View File

@@ -14,13 +14,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Upload latest strings for translation on Crowdin
uses: crowdin/github-action@v2.5.0
uses: crowdin/github-action@0749939f635900a2521aa6aac7a3766642b2dc71 # v2.11.0
with:
crowdin_branch_name: main
config: '.github/crowdin.yml'

View File

@@ -19,16 +19,16 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
uses: docker/build-push-action@v6.10.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
build-args: CONFIGURATION=${{ matrix.configuration }}
context: .

View File

@@ -18,23 +18,23 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to ghcr.io
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -50,7 +50,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@v6.10.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
file: Dockerfile.Service

View File

@@ -19,23 +19,23 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to ghcr.io
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -50,7 +50,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@v6.10.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
platforms: ${{ env.PLATFORMS }}
@@ -69,7 +69,7 @@ jobs:
push: true
- name: Update DockerHub repository description
uses: peter-evans/dockerhub-description@v4.0.0
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -19,23 +19,23 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to ghcr.io
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -51,7 +51,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@v6.10.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
platforms: ${{ env.PLATFORMS }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Lock inactive threads
uses: dessant/lock-threads@v5.0.1
uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
with:
discussion-inactive-days: 90
issue-inactive-days: 60

View File

@@ -19,13 +19,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
- name: Setup Node.js with npm
uses: actions/setup-node@v4.1.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
check-latest: true
node-version: ${{ env.NODE_JS_VERSION }}
@@ -43,8 +43,9 @@ jobs:
run: npm run-script deploy --no-progress --prefix ASF-ui
- name: Upload ASF-ui
uses: actions/upload-artifact@v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
compression-level: 1
if-no-files-found: error
name: ASF-ui
path: ASF-ui/dist
@@ -73,7 +74,6 @@ jobs:
- os: windows-latest
variant: win-x64
environment: build
runs-on: ${{ matrix.os }}
permissions:
@@ -82,12 +82,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.2.0
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
@@ -95,7 +95,7 @@ jobs:
run: dotnet --info
- name: Download previously built ASF-ui
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ASF-ui
path: ASF-ui/dist
@@ -362,13 +362,14 @@ jobs:
- name: Generate artifact attestation for ASF-${{ matrix.variant }}.zip
if: ${{ github.event_name == 'push' }}
uses: actions/attest-build-provenance@v2.1.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: out/ASF-${{ matrix.variant }}.zip
- name: Upload ASF-${{ matrix.variant }}
uses: actions/upload-artifact@v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
compression-level: 1
if-no-files-found: error
name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
path: out/ASF-${{ matrix.variant }}.zip
@@ -409,14 +410,15 @@ jobs:
- name: Generate artifact attestation for ArchiSteamFarm.OfficialPlugins.Monitoring
if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.variant == 'generic' }}
uses: actions/attest-build-provenance@v2.1.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: out/ArchiSteamFarm.OfficialPlugins.Monitoring.zip
- name: Upload ArchiSteamFarm.OfficialPlugins.Monitoring
if: ${{ matrix.os == 'ubuntu-latest' && matrix.variant == 'generic' }}
uses: actions/upload-artifact@v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
compression-level: 1
if-no-files-found: error
name: ArchiSteamFarm.OfficialPlugins.Monitoring
path: out/ArchiSteamFarm.OfficialPlugins.Monitoring.zip
@@ -434,66 +436,66 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
- name: Download ASF-generic artifact from ubuntu-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ubuntu-latest_ASF-generic
path: out
- name: Download ASF-linux-arm artifact from ubuntu-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ubuntu-latest_ASF-linux-arm
path: out
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ubuntu-latest_ASF-linux-arm64
path: out
- name: Download ASF-linux-x64 artifact from ubuntu-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ubuntu-latest_ASF-linux-x64
path: out
- name: Download ASF-osx-arm64 artifact from macos-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: macos-latest_ASF-osx-arm64
path: out
- name: Download ASF-osx-x64 artifact from macos-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: macos-latest_ASF-osx-x64
path: out
- name: Download ASF-win-arm64 artifact from windows-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: windows-latest_ASF-win-arm64
path: out
- name: Download ASF-win-x64 artifact from windows-latest
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: windows-latest_ASF-win-x64
path: out
- name: Download ArchiSteamFarm.OfficialPlugins.Monitoring artifact
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ArchiSteamFarm.OfficialPlugins.Monitoring
path: out
- name: Import GPG key for signing
uses: crazy-max/ghaction-import-gpg@v6.2.0
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
with:
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
@@ -507,36 +509,39 @@ jobs:
gpg -a -b -o SHA512SUMS.sign SHA512SUMS
- name: Generate artifact attestation for SHA512SUMS
uses: actions/attest-build-provenance@v2.1.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: out/SHA512SUMS
- name: Upload SHA512SUMS
uses: actions/upload-artifact@v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
compression-level: 1
if-no-files-found: error
name: SHA512SUMS
path: out/SHA512SUMS
- name: Generate artifact attestation for SHA512SUMS.sign
uses: actions/attest-build-provenance@v2.1.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-path: out/SHA512SUMS.sign
- name: Upload SHA512SUMS.sign
uses: actions/upload-artifact@v4.5.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
compression-level: 1
if-no-files-found: error
name: SHA512SUMS.sign
path: out/SHA512SUMS.sign
- name: Create ArchiSteamFarm GitHub release
uses: ncipollo/release-action@v1.14.0
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
with:
allowUpdates: true
artifactErrorsFailBuild: true
artifacts: "out/*"
bodyFile: .github/RELEASE_TEMPLATE.md
immutableCreate: true
makeLatest: false
name: ArchiSteamFarm V${{ github.ref_name }}
prerelease: true

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
show-progress: false
submodules: recursive
@@ -27,11 +27,11 @@ jobs:
run: |
set -eu
git fetch --depth=1 origin master
git fetch origin master
git reset --hard origin/master
- name: Download latest translations from Crowdin
uses: crowdin/github-action@v2.5.0
uses: crowdin/github-action@0749939f635900a2521aa6aac7a3766642b2dc71 # v2.11.0
with:
upload_sources: false
download_translations: true
@@ -43,7 +43,7 @@ jobs:
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
- name: Import GPG key for signing
uses: crazy-max/ghaction-import-gpg@v6.2.0
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
with:
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
git_config_global: true

2
ASF-ui

Submodule ASF-ui updated: bd81c92fc4...96606b83ed

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
</ItemGroup>

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
</ItemGroup>

View File

@@ -4,8 +4,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp.XPath" IncludeAssets="compile" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="AngleSharp" IncludeAssets="compile" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
</ItemGroup>

View File

@@ -66,7 +66,7 @@ public sealed class SignInWithSteamController : ArchiController {
return StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries)));
}
IAttr? paramsNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='openidparams']/@value");
IElement? paramsNode = challengeResponse.Content.QuerySelector("input[name='openidparams'][value]");
if (paramsNode == null) {
ASF.ArchiLogger.LogNullError(paramsNode);
@@ -74,7 +74,7 @@ public sealed class SignInWithSteamController : ArchiController {
return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(paramsNode))));
}
string paramsValue = paramsNode.Value;
string? paramsValue = paramsNode.GetAttribute("value");
if (string.IsNullOrEmpty(paramsValue)) {
ASF.ArchiLogger.LogNullError(paramsValue);
@@ -82,7 +82,7 @@ public sealed class SignInWithSteamController : ArchiController {
return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(paramsValue))));
}
IAttr? nonceNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='nonce']/@value");
IElement? nonceNode = challengeResponse.Content.QuerySelector("input[name='nonce'][value]");
if (nonceNode == null) {
ASF.ArchiLogger.LogNullError(nonceNode);
@@ -90,7 +90,7 @@ public sealed class SignInWithSteamController : ArchiController {
return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, Strings.FormatErrorObjectIsNull(nameof(nonceNode))));
}
string nonceValue = nonceNode.Value;
string? nonceValue = nonceNode.GetAttribute("value");
if (string.IsNullOrEmpty(nonceValue)) {
ASF.ArchiLogger.LogNullError(nonceValue);

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" PrivateAssets="all" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />

View File

@@ -38,56 +38,54 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
internal sealed class BotCache : SerializableFile {
[JsonDisallowNull]
[JsonInclude]
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
internal ConcurrentList<AssetForListing> LastAnnouncedAssetsForListing { get; private init; } = [];
[JsonInclude]
[JsonPropertyName("BackingLastAnnouncedTradeToken")]
internal string? LastAnnouncedTradeToken {
get => BackingLastAnnouncedTradeToken;
get;
set {
if (BackingLastAnnouncedTradeToken == value) {
if (field == value) {
return;
}
BackingLastAnnouncedTradeToken = value;
field = value;
Utilities.InBackground(Save);
}
}
[JsonInclude]
[JsonPropertyName("BackingLastInventoryChecksumBeforeDeduplication")]
internal string? LastInventoryChecksumBeforeDeduplication {
get => BackingLastInventoryChecksumBeforeDeduplication;
get;
set {
if (BackingLastInventoryChecksumBeforeDeduplication == value) {
if (field == value) {
return;
}
BackingLastInventoryChecksumBeforeDeduplication = value;
field = value;
Utilities.InBackground(Save);
}
}
[JsonInclude]
[JsonPropertyName("BackingLastRequestAt")]
internal DateTime? LastRequestAt {
get => BackingLastRequestAt;
get;
set {
if (BackingLastRequestAt == value) {
if (field == value) {
return;
}
BackingLastRequestAt = value;
field = value;
Utilities.InBackground(Save);
}
}
[JsonInclude]
private string? BackingLastAnnouncedTradeToken { get; set; }
[JsonInclude]
private string? BackingLastInventoryChecksumBeforeDeduplication { get; set; }
[JsonInclude]
private DateTime? BackingLastRequestAt { get; set; }
private BotCache(string filePath) : this() {
ArgumentException.ThrowIfNullOrEmpty(filePath);
@@ -97,18 +95,18 @@ internal sealed class BotCache : SerializableFile {
[JsonConstructor]
private BotCache() => LastAnnouncedAssetsForListing.OnModified += OnObjectModified;
[UsedImplicitly]
public bool ShouldSerializeBackingLastAnnouncedTradeToken() => !string.IsNullOrEmpty(BackingLastAnnouncedTradeToken);
[UsedImplicitly]
public bool ShouldSerializeBackingLastInventoryChecksumBeforeDeduplication() => !string.IsNullOrEmpty(BackingLastInventoryChecksumBeforeDeduplication);
[UsedImplicitly]
public bool ShouldSerializeBackingLastRequestAt() => BackingLastRequestAt.HasValue;
[UsedImplicitly]
public bool ShouldSerializeLastAnnouncedAssetsForListing() => LastAnnouncedAssetsForListing.Count > 0;
[UsedImplicitly]
public bool ShouldSerializeLastAnnouncedTradeToken() => !string.IsNullOrEmpty(LastAnnouncedTradeToken);
[UsedImplicitly]
public bool ShouldSerializeLastInventoryChecksumBeforeDeduplication() => !string.IsNullOrEmpty(LastInventoryChecksumBeforeDeduplication);
[UsedImplicitly]
public bool ShouldSerializeLastRequestAt() => LastRequestAt.HasValue;
protected override void Dispose(bool disposing) {
if (disposing) {
// Events we registered

View File

@@ -64,6 +64,10 @@
<value>Matched totalt {0} sæt denne runde.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Annoncerer {0} ({1}) med inventar lavet af {2} varer i alt på listen...</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>Matchede i alt {0} varer med bot {1} ({2}), sender handelstilbud...</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>

View File

@@ -64,4 +64,16 @@
<value>Ο τελικός αριθμός συνόλων που έχουν ταιριάξει είναι {0}, αυτόν τον γύρο.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Ανακοίνωση {0} ({1}) με απογραφή από στοιχεία {2} συνολικά στην καταχώρηση...</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>Ταίριαξε ένα σύνολο αντικειμένων {0} με bot {1} ({2}), στέλνοντας προσφορά συναλλαγής...</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>Αποτυχία αποστολής μιας προσφοράς συναλλαγής στο bot {0} ({1}), με κίνηση...</value>
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
</data>
</root>

View File

@@ -64,6 +64,10 @@
<value>Abbinati un totale di {0} set questo round.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Annuncio {0} ({1}) con inventario fatto di elementi {2} in totale sulla lista...</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>Abbinato un totale di {0} elementi tramite bot {1} ({2}), inviando un'offerta commerciale...</value>
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>

View File

@@ -64,4 +64,20 @@
<value>すべてのうち、{0} セットにマッチしたためこのラウンドを終了します。</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>{0}{1})をリスト上でアナウンス中です。インベントリには合計 {2} 個のアイテムが含まれています…</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>ボット {1}{2})と合計 {0} 個のアイテムが一致しました。トレードオファーを送信しています…</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>ボット {0} ({1})にトレードオファーを送信できませんでした。次に進みます…</value>
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
</data>
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
<value>いくつかの確認に失敗しました。 {0} の取引から約 {1} が正常に送信されました。</value>
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
</data>
</root>

View File

@@ -61,15 +61,15 @@
</value>
</resheader>
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
<value>{0} conjunto(s) foi(foram) associado(s) nessa rodada.</value>
<value>Total de {0} conjunto(s) correspondido(s) nesta rodada.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Anunciando {0} ({1}) com um inventário composto pelo total de {2} itens listados...</value>
<value>Anunciando {0} ({1}) com um inventário de {2} itens no total na listagem...</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>{0} item(ns) correspondidos(s) com o bot {1} ({2}), enviando oferta de troca...</value>
<value>{0} item(ns) correspondido(s) com o bot {1} ({2}), enviando proposta de troca...</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">
@@ -77,7 +77,7 @@
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
</data>
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
<value>Algumas confirmações falharam, cerca de {0} de {1} operações foram enviadas com sucesso.</value>
<value>Algumas confirmações falharam, aproximadamente {0} de {1} trocas foram enviadas com sucesso.</value>
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
</data>
</root>

View File

@@ -60,4 +60,24 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ActivelyMatchingItemsRound" xml:space="preserve">
<value>Matchade totalt {0} sets denna runda.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Meddela {0} ({1}) med lager gjorda av {2} objekt totalt på listan...</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>Matchade totalt {0} objekt med bot {1} ({2}), skickar handels-erbjudande...</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>Det gick inte att skicka ett byteserbjudande till bot {0} ({1}), går vidare...</value>
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
</data>
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
<value>Vissa bekräftelser har misslyckats, cirka {0} av {1} skickades framgångsrikt.</value>
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
</data>
</root>

View File

@@ -60,14 +60,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
private const byte MinimumPasswordResetCooldownDays = 5; // As imposed by Steam limits
private const byte MinimumSteamGuardEnabledDays = 15; // As imposed by Steam limits
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
private const byte MinPersonaStateTTL = MinAnnouncementTTL; // Minimum amount of minutes we must wait before requesting persona state update
private static readonly FrozenSet<EAssetType> AcceptedMatchableTypes = new HashSet<EAssetType>(4) {
private static readonly FrozenSet<EAssetType> AcceptedMatchableTypes = [
EAssetType.Emoticon,
EAssetType.FoilTradingCard,
EAssetType.ProfileBackground,
EAssetType.TradingCard
}.ToFrozenSet();
];
private readonly Bot Bot;
private readonly Timer? HeartBeatTimer;
@@ -187,7 +187,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
return;
}
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
if (DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) {
return;
}
@@ -199,7 +199,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if ((DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) && ShouldSendHeartBeats) {
if (DateTime.UtcNow < LastAnnouncement.AddMinutes(ShouldSendAnnouncementEarlier ? MinAnnouncementTTL : MaxAnnouncementTTL)) {
return;
}
@@ -233,6 +233,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
// Should never happen, since IsEligibleForListing() check above ensured we have at least one matchable type
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
}
@@ -905,9 +906,8 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
Bot.ArchiLogger.LogNullError(acceptedMatchableTypes);
return;
// Should never happen, since IsEligibleForMatching() check above ensured we have at least one matchable type
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
}
if (!await MatchActivelySemaphore.WaitAsync(0).ConfigureAwait(false)) {
@@ -1276,20 +1276,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
Bot.ArchiLogger.LogGenericTrace($"{listedUser.SteamID}...");
byte? tradeHoldDuration = await Bot.ArchiWebHandler.GetCombinedTradeHoldDurationAgainstUser(listedUser.SteamID, listedUser.TradeToken).ConfigureAwait(false);
switch (tradeHoldDuration) {
case null:
Bot.ArchiLogger.LogGenericTrace(Strings.FormatErrorIsEmpty(nameof(tradeHoldDuration)));
continue;
case > 0 when (tradeHoldDuration.Value > maxTradeHoldDuration) || (tradeHoldDuration.Value > listedUser.MaxTradeHoldDuration):
Bot.ArchiLogger.LogGenericTrace($"{tradeHoldDuration.Value} > {maxTradeHoldDuration} || {listedUser.MaxTradeHoldDuration}");
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 EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity))).Select(static asset => asset.ToAsset()).ToHashSet();
if (theirInventory.Count == 0) {
continue;
@@ -1297,6 +1284,8 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
skippedSetsThisUser.Clear();
byte? tradeHoldDuration = null;
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> theirTradableState = MatchingUtilities.GetTradableInventoryState(theirInventory);
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
@@ -1335,6 +1324,10 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
match = false;
foreach ((ulong ourItem, uint ourFullAmount) in ourFullSet.Where(static item => item.Value > 1).OrderByDescending(static item => item.Value)) {
if ((tradeHoldDuration > maxTradeHoldDuration) || (tradeHoldDuration > listedUser.MaxTradeHoldDuration)) {
break;
}
if (!ourTradableSet.TryGetValue(ourItem, out uint ourTradableAmount) || (ourTradableAmount == 0)) {
continue;
}
@@ -1378,6 +1371,29 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
}
}
// Now that we have a match, check trade hold to ensure user is valid to match against
// Since it involves remote call, we didn't do it previously, to skip checking against users with no matches for us
if (tradeHoldDuration == null) {
tradeHoldDuration = await Bot.ArchiWebHandler.GetCombinedTradeHoldDurationAgainstUser(listedUser.SteamID, listedUser.TradeToken).ConfigureAwait(false);
if (tradeHoldDuration == null) {
Bot.ArchiLogger.LogGenericTrace(Strings.FormatErrorIsEmpty(nameof(tradeHoldDuration)));
break;
}
if ((tradeHoldDuration.Value > maxTradeHoldDuration) || (tradeHoldDuration.Value > listedUser.MaxTradeHoldDuration)) {
Bot.ArchiLogger.LogGenericTrace($"{tradeHoldDuration.Value} > {maxTradeHoldDuration} || {listedUser.MaxTradeHoldDuration}");
break;
}
}
if ((tradeHoldDuration.Value > 0) && set.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(set.RealAppID)) {
// We're not considering this set for matching due to trade hold
continue;
}
// Skip this set from the remaining of this round
skippedSetsThisTrade.Add(set);

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" PrivateAssets="all" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />

View File

@@ -135,12 +135,6 @@ internal static class Commands {
mobileAuthenticator.Init(bot);
MobileAuthenticatorHandler? mobileAuthenticatorHandler = bot.GetHandler<MobileAuthenticatorHandler>();
if (mobileAuthenticatorHandler == null) {
throw new InvalidOperationException(nameof(mobileAuthenticatorHandler));
}
ulong steamTime = await mobileAuthenticator.GetSteamTime().ConfigureAwait(false);
string? code = mobileAuthenticator.GenerateTokenForTime(steamTime);
@@ -149,10 +143,10 @@ internal static class Commands {
return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(mobileAuthenticator.GenerateTokenForTime)));
}
CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code, steamTime).ConfigureAwait(false);
CTwoFactor_FinalizeAddAuthenticator_Response? response = await MobileAuthenticatorWebHandler.FinalizeAuthenticator(bot, activationCode, code, steamTime).ConfigureAwait(false);
if (response == null) {
return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(mobileAuthenticatorHandler.FinalizeAuthenticator)));
return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(MobileAuthenticatorWebHandler.FinalizeAuthenticator)));
}
if (!response.success) {
@@ -319,18 +313,12 @@ internal static class Commands {
return bot.Commands.FormatBotResponse(Strings.BotNotConnected);
}
MobileAuthenticatorHandler? mobileAuthenticatorHandler = bot.GetHandler<MobileAuthenticatorHandler>();
if (mobileAuthenticatorHandler == null) {
throw new InvalidOperationException(nameof(mobileAuthenticatorHandler));
}
string deviceID = $"android:{Guid.NewGuid()}";
CTwoFactor_AddAuthenticator_Response? response = await mobileAuthenticatorHandler.AddAuthenticator(bot.SteamID, deviceID).ConfigureAwait(false);
CTwoFactor_AddAuthenticator_Response? response = await MobileAuthenticatorWebHandler.AddAuthenticator(bot, deviceID).ConfigureAwait(false);
if (response == null) {
return bot.Commands.FormatBotResponse(Strings.WarningFailed);
return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(MobileAuthenticatorWebHandler.AddAuthenticator)));
}
EResult result = (EResult) response.status;

View File

@@ -1,118 +0,0 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.NLog;
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
internal sealed class MobileAuthenticatorHandler : ClientMsgHandler {
private readonly ArchiLogger ArchiLogger;
private readonly TwoFactor UnifiedTwoFactorService;
internal MobileAuthenticatorHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnifiedMessages) {
ArgumentNullException.ThrowIfNull(archiLogger);
ArgumentNullException.ThrowIfNull(steamUnifiedMessages);
ArchiLogger = archiLogger;
UnifiedTwoFactorService = steamUnifiedMessages.CreateService<TwoFactor>();
}
public override void HandleMsg(IPacketMsg packetMsg) => ArgumentNullException.ThrowIfNull(packetMsg);
internal async Task<CTwoFactor_AddAuthenticator_Response?> AddAuthenticator(ulong steamID, string deviceID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
throw new ArgumentOutOfRangeException(nameof(steamID));
}
ArgumentException.ThrowIfNullOrEmpty(deviceID);
if (Client == null) {
throw new InvalidOperationException(nameof(Client));
}
if (!Client.IsConnected) {
return null;
}
CTwoFactor_AddAuthenticator_Request request = new() {
authenticator_type = 1,
authenticator_time = Utilities.GetUnixTime(),
device_identifier = deviceID,
steamid = steamID
};
SteamUnifiedMessages.ServiceMethodResponse<CTwoFactor_AddAuthenticator_Response> response;
try {
response = await UnifiedTwoFactorService.AddAuthenticator(request).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
return null;
}
return response.Result == EResult.OK ? response.Body : null;
}
internal async Task<CTwoFactor_FinalizeAddAuthenticator_Response?> FinalizeAuthenticator(ulong steamID, string activationCode, string authenticatorCode, ulong authenticatorTime) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
throw new ArgumentOutOfRangeException(nameof(steamID));
}
ArgumentException.ThrowIfNullOrEmpty(activationCode);
ArgumentException.ThrowIfNullOrEmpty(authenticatorCode);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(authenticatorTime);
if (Client == null) {
throw new InvalidOperationException(nameof(Client));
}
if (!Client.IsConnected) {
return null;
}
CTwoFactor_FinalizeAddAuthenticator_Request request = new() {
activation_code = activationCode,
authenticator_code = authenticatorCode,
authenticator_time = authenticatorTime,
steamid = steamID
};
SteamUnifiedMessages.ServiceMethodResponse<CTwoFactor_FinalizeAddAuthenticator_Response> response;
try {
response = await UnifiedTwoFactorService.FinalizeAddAuthenticator(request).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
return null;
}
return response.Result == EResult.OK ? response.Body : null;
}
}

View File

@@ -22,7 +22,6 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
@@ -39,7 +38,7 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
[Export(typeof(IPlugin))]
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient {
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2 {
[JsonInclude]
public override string Name => nameof(MobileAuthenticatorPlugin);
@@ -66,25 +65,6 @@ internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2,
return await Commands.OnBotCommand(bot, access, message, args, steamID).ConfigureAwait(false);
}
public Task OnBotSteamCallbacksInit(Bot bot, CallbackManager callbackManager) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(callbackManager);
return Task.CompletedTask;
}
public Task<IReadOnlyCollection<ClientMsgHandler>?> OnBotSteamHandlersInit(Bot bot) {
ArgumentNullException.ThrowIfNull(bot);
SteamUnifiedMessages? steamUnifiedMessages = bot.GetHandler<SteamUnifiedMessages>();
if (steamUnifiedMessages == null) {
throw new InvalidOperationException(nameof(steamUnifiedMessages));
}
return Task.FromResult<IReadOnlyCollection<ClientMsgHandler>?>(new HashSet<ClientMsgHandler>(1) { new MobileAuthenticatorHandler(bot.ArchiLogger, steamUnifiedMessages) });
}
public override Task OnLoaded() {
Utilities.WarnAboutIncompleteTranslation(Strings.ResourceManager);

View File

@@ -0,0 +1,174 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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.Net.Http;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Integration;
using ArchiSteamFarm.Web;
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
internal static class MobileAuthenticatorWebHandler {
private const string TwoFactorService = "ITwoFactorService";
internal static async Task<CTwoFactor_AddAuthenticator_Response?> AddAuthenticator(Bot bot, string deviceID) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentException.ThrowIfNullOrEmpty(deviceID);
if (!bot.IsConnectedAndLoggedOn) {
return null;
}
string? accessToken = bot.AccessToken;
if (string.IsNullOrEmpty(accessToken)) {
return null;
}
const string endpoint = "AddAuthenticator";
HttpMethod method = HttpMethod.Post;
CTwoFactor_AddAuthenticator_Request request = new() {
authenticator_time = Utilities.GetUnixTime(),
authenticator_type = 1,
device_identifier = deviceID,
steamid = bot.SteamID
};
Dictionary<string, object?> arguments = new(1, StringComparer.Ordinal) {
{ "access_token", accessToken }
};
using WebAPI.AsyncInterface twoFactorService = bot.SteamConfiguration.GetAsyncWebAPIInterface(TwoFactorService);
twoFactorService.Timeout = bot.ArchiWebHandler.WebBrowser.Timeout;
WebAPI.WebAPIResponse<CTwoFactor_AddAuthenticator_Response>? response = null;
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
if ((i > 0) && (ArchiWebHandler.WebLimiterDelay > 0)) {
await Task.Delay(ArchiWebHandler.WebLimiterDelay).ConfigureAwait(false);
}
if (Debugging.IsUserDebugging) {
bot.ArchiLogger.LogGenericDebug($"{method} {bot.SteamConfiguration.WebAPIBaseAddress}{TwoFactorService}/{endpoint}");
}
try {
response = await ArchiWebHandler.WebLimitRequest(
bot.SteamConfiguration.WebAPIBaseAddress,
// ReSharper disable once AccessToDisposedClosure
async () => await twoFactorService.CallProtobufAsync<CTwoFactor_AddAuthenticator_Response, CTwoFactor_AddAuthenticator_Request>(method, endpoint, request, extraArgs: arguments).ConfigureAwait(false)
).ConfigureAwait(false);
} catch (TaskCanceledException e) {
bot.ArchiLogger.LogGenericDebuggingException(e);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
}
}
if (response == null) {
bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries));
return null;
}
return response.Body;
}
internal static async Task<CTwoFactor_FinalizeAddAuthenticator_Response?> FinalizeAuthenticator(Bot bot, string activationCode, string authenticatorCode, ulong authenticatorTime) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentException.ThrowIfNullOrEmpty(activationCode);
ArgumentException.ThrowIfNullOrEmpty(authenticatorCode);
ArgumentOutOfRangeException.ThrowIfZero(authenticatorTime);
if (!bot.IsConnectedAndLoggedOn) {
return null;
}
string? accessToken = bot.AccessToken;
if (string.IsNullOrEmpty(accessToken)) {
return null;
}
const string endpoint = "FinalizeAddAuthenticator";
HttpMethod method = HttpMethod.Post;
CTwoFactor_FinalizeAddAuthenticator_Request request = new() {
activation_code = activationCode,
authenticator_code = authenticatorCode,
authenticator_time = authenticatorTime,
steamid = bot.SteamID
};
Dictionary<string, object?> arguments = new(1, StringComparer.Ordinal) {
{ "access_token", accessToken }
};
using WebAPI.AsyncInterface twoFactorService = bot.SteamConfiguration.GetAsyncWebAPIInterface(TwoFactorService);
twoFactorService.Timeout = bot.ArchiWebHandler.WebBrowser.Timeout;
WebAPI.WebAPIResponse<CTwoFactor_FinalizeAddAuthenticator_Response>? response = null;
for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
if ((i > 0) && (ArchiWebHandler.WebLimiterDelay > 0)) {
await Task.Delay(ArchiWebHandler.WebLimiterDelay).ConfigureAwait(false);
}
if (Debugging.IsUserDebugging) {
bot.ArchiLogger.LogGenericDebug($"{method} {bot.SteamConfiguration.WebAPIBaseAddress}{TwoFactorService}/{endpoint}");
}
try {
response = await ArchiWebHandler.WebLimitRequest(
bot.SteamConfiguration.WebAPIBaseAddress,
// ReSharper disable once AccessToDisposedClosure
async () => await twoFactorService.CallProtobufAsync<CTwoFactor_FinalizeAddAuthenticator_Response, CTwoFactor_FinalizeAddAuthenticator_Request>(method, endpoint, request, extraArgs: arguments).ConfigureAwait(false)
).ConfigureAwait(false);
} catch (TaskCanceledException e) {
bot.ArchiLogger.LogGenericDebuggingException(e);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
}
}
if (response == null) {
bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries));
return null;
}
return response.Body;
}
}

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />

View File

@@ -47,11 +47,9 @@ namespace ArchiSteamFarm.OfficialPlugins.Monitoring;
[Export(typeof(IPlugin))]
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialGitHubPluginUpdates, IWebInterface, IWebServiceProvider, IBotTradeOfferResults {
internal sealed class MonitoringPlugin : OfficialPlugin, IBot, IBotTradeOfferResults, IDisposable, IOfficialGitHubPluginUpdates, IWebInterface, IWebServiceProvider {
private const string MeterName = SharedInfo.AssemblyName;
private const string MetricNamePrefix = "asf";
private const string UnknownLabelValueFallback = "unknown";
private static readonly Measurement<byte> BuildInfo = new(
@@ -85,6 +83,20 @@ internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialG
public void Dispose() => Meter?.Dispose();
public Task OnBotDestroy(Bot bot) {
ArgumentNullException.ThrowIfNull(bot);
TradeStatistics.TryRemove(bot, out _);
return Task.CompletedTask;
}
public Task OnBotInit(Bot bot) {
ArgumentNullException.ThrowIfNull(bot);
return Task.CompletedTask;
}
public Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<ParseTradeResult> tradeResults) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(tradeResults);
@@ -117,8 +129,11 @@ internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialG
InitializeMeter();
services.AddOpenTelemetry().WithMetrics(
builder => {
if (Meter == null) {
throw new InvalidOperationException(nameof(Meter));
}
services.AddOpenTelemetry().WithMetrics(builder => {
builder.AddPrometheusExporter(static config => config.ScrapeEndpointPath = "/Api/metrics");
builder.AddRuntimeInstrumentation();
builder.AddAspNetCoreInstrumentation();
@@ -130,17 +145,18 @@ internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialG
public override Task OnLoaded() => Task.CompletedTask;
[MemberNotNull(nameof(Meter))]
private void InitializeMeter() {
if (Meter != null) {
return;
}
PluginMeasurements = new HashSet<Measurement<int>>(3) {
new(PluginsCore.ActivePlugins.Count),
new(PluginsCore.ActivePlugins.Count(static plugin => plugin is OfficialPlugin), new KeyValuePair<string, object?>(TagNames.PluginType, "official")),
new(PluginsCore.ActivePlugins.Count(static plugin => plugin is not OfficialPlugin), new KeyValuePair<string, object?>(TagNames.PluginType, "custom"))
}.ToFrozenSet();
int officialPluginCount = PluginsCore.ActivePlugins.Count(static plugin => plugin is OfficialPlugin);
PluginMeasurements = [
new Measurement<int>(PluginsCore.ActivePlugins.Count),
new Measurement<int>(officialPluginCount, new KeyValuePair<string, object?>(TagNames.PluginType, "official")),
new Measurement<int>(PluginsCore.ActivePlugins.Count - officialPluginCount, new KeyValuePair<string, object?>(TagNames.PluginType, "custom"))
];
Meter = new Meter(MeterName, Version.ToString());
@@ -255,8 +271,7 @@ internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialG
);
Meter.CreateObservableCounter(
$"{MetricNamePrefix}_bot_trades", () => TradeStatistics.SelectMany<KeyValuePair<Bot, TradeStatistics>, Measurement<int>>(
static kv => [
$"{MetricNamePrefix}_bot_trades", () => TradeStatistics.SelectMany<KeyValuePair<Bot, TradeStatistics>, Measurement<int>>(static kv => [
new Measurement<int>(
kv.Value.AcceptedOffers,
new KeyValuePair<string, object?>(TagNames.BotName, kv.Key.BotName),

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" PrivateAssets="all" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />

View File

@@ -107,7 +107,10 @@
<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="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Dokončeno {0} z {1} požadavků na sklad</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -60,33 +60,115 @@
<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} er blevet deaktiveret på grund af en manglende build token</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} er i øjeblikket deaktiveret i henhold til din konfiguration. Hvis du vil hjælpe SteamDB i dataindsendelse, så tjek vores 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} er blevet initialiseret med succes, tak på forhånd for din hjælp. Den første indsendelse vil ske i cirka {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} kunne ikke indlæses, en ny instans vil blive initialiseret...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Der er ingen apps, der kræver en opdatering i denne bot instans.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Henter i alt {0} app-adgangstokens...</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>Henter {0} app-adgangstokens...</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>Færdig med at hente {0} app-adgangstegn.</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>Færdig med at hente i alt {0} app-adgangstokiner.</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>Henter alle depoter for i alt {0} apps...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Retrieving {0} app infos...</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>Færdig med at hente {0} app-info.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Færdig {0} ud af {1} depot nøgleanmodninger.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Færdig med at hente alle depot nøgler for i alt {0} apps.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Der er ingen nye data at indsende, alt er opdateret.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Kunne ikke indsende data, da der ikke er noget gyldigt SteamID sæt, som vi kunne klassificere som bidragsyder. Overvej opsætning af {0} egenskab.</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>Indsender i alt registrerede apps/pakker/depots: {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>Indsendelsen er mislykkedes på grund af for mange anmodninger, vi vil prøve igen om cirka {0} fra nu.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Dataene er blevet indsendt. Serveren har registreret i alt nye apps/packages/depots: {0} ({1} verificeret)/{2} ({3} verificeret)/{4} ({5} verificeret).</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>Nye apps: {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>Verificerede apps: {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>Nye pakker: {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>Verificerede pakker: {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>Nye depoter: {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>Verificerede 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} initialiseret, plugin vil ikke løse nogen af dem: {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>Indlæser STD global cache...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validerer STD global cache integritet...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Mislykkedes at verificere STD global cache integritet. Dette tyder på en potentiel fil / hukommelse korruption, en ny instans vil blive initialiseret i stedet.</value>
</data>
</root>

View File

@@ -107,7 +107,10 @@
<value>Abruf von {0} App-Infos abgeschlossen.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>{0} von {1} Depot-Schlüsselanfragen beendet.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Abruf aller Depotschlüssel für insgesamt {0} Apps abgeschlossen.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -68,7 +68,10 @@
<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>
@@ -104,7 +107,10 @@
<value>Ολοκληρώθηκε η ανάκτηση {0} πληροφοριών εφαρμογών.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Ολοκληρώθηκε το {0} από τα αιτήματα κλειδιού depot {1}.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -68,7 +68,10 @@
<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äiseen lähetykseen on noin {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} ei voitu ladata. Uusi instanssi alustetaan...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
@@ -104,7 +107,10 @@
<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="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Suoritettiin {0}/{1} depot-avaimien pyynnöistä.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -107,7 +107,10 @@
<value>Récupération de {0} infos d'application terminée.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Récupération de {0} sur {1} demandes de clés de dépôt terminée.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Fin de la récupération de toutes les clés de dépôts pour un total de {0} applications.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -107,7 +107,10 @@
<value>Selesai memuat {0} informasi aplikasi.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Selesai {0} dari {1} depot key yang di minta.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Selesai memuat semua kunci depot untuk total {0} aplikasi.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -107,7 +107,10 @@
<value>Hai completato il recupero di {0} informazioni app.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Hai completato le richieste di chiave del deposito {0} su {1}.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -60,25 +60,80 @@
<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>このボットでは更新が必要なアプリはありません。</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="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>{1} 件中 {0} 件のデポキーリクエストが完了しました。</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>寄稿者として分類可能な有効な Steam Id が設定されていないため、データを送信できませんでした。{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>
@@ -95,10 +150,25 @@
<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>

View File

@@ -121,7 +121,9 @@
<data name="LoadingGlobalCache" xml:space="preserve">
<value>STD 글로벌 캐시 로드 중...</value>
</data>
</root>

View File

@@ -107,7 +107,10 @@
<value>Pabeidza iegūt {0} aplikāciju informāciju.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Pabeigts {0} no {1} noliktavas atslēgu pieprasījumiem.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Pabeigta visu depot atslēgu izgūšana, kopā {0} lietotnēm.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -68,7 +68,10 @@
<value>{0} is momenteel uitgeschakeld volgens uw configuratie. Als je SteamDB wilt helpen bij het indienen van gegevens, bekijk dan onze 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} is met succes geïnitialiseerd. Alvast bedankt voor uw hulp. De eerste uitwerking zal gebeuren in ongeveer {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} kon niet worden geladen, een nieuwe instantie zal worden geïnitialiseerd...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
@@ -104,13 +107,33 @@
<value>Ophalen van {0} app-infos voltooid.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Klaar {0} van {1} depot sleutelverzoeken.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Ophalen van alle depot keys voltooid voor een totaal van {0} apps.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Er zijn geen nieuwe gegevens om in te dienen, alles is up-to-date.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Kon de gegevens niet indienen omdat er geen geldige SteamID is die we als bijdrager kunnen classificeren. Overweeg het instellen van {0} property.</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>Verzenden van een totaal van geregistreerde apps/packages/depos: {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>De indiening is mislukt door te veel verzonden, we zullen het vanaf nu opnieuw proberen in ongeveer {0}.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>De gegevens zijn succesvol ingediend. De server heeft een totaal van nieuwe apps/pakketten/depots: {0} ({1} geverifieerd)/{2} ({3} geverifieerd)/{4} ({5} geverifieerd).</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>Nieuwe apps: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
@@ -127,10 +150,25 @@
<value>Geverifieerde pakketten: {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>Nieuwe depoten: {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>Geverifieerde 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} geïnitialiseerd, de plugin zal geen van deze oplossen: {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 global cache laden...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Valideren van STD globale cache integriteit...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Kon de wereldwijde STD cache integriteit niet verifiëren. Dit suggereert een potentiële bestands/geheugencorruptie, een nieuw exemplaar zal in plaats daarvan worden geïnitialiseerd.</value>
</data>
</root>

View File

@@ -60,33 +60,112 @@
<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} har blitt deaktivert på grunn av et manglende byggetoken</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} er for øyeblikket deaktivert i henhold til din konfigurasjon. Hvis du vil hjelpe SteamDB med datainnsending, vennligst sjekk ut vår 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} har blitt igangsatt vellykket, takk for din hjelp. Den første innsending vil skje i omtrent {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} kunne ikke lastes inn, en frisk forekomst vil bli initialisert...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Det er ingen programmer som krever en oppdatering på denne botthendelsen.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Henter de totale {0} app-tilgangstegener...</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>Henter {0} app-tilgangstokene...</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>Ferdig med å hente {0} app-tilgangsnøkkler.</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>Ferdig med å hente totalt {0} app-tilgangsnøkkler.</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>Henter alle depoter for en total {0} apper...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Retrieving {0} app infos...</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>Ferdig med å hente {0} app infos.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Ferdig med {0} av {1} lagerets viktigste forespørsler.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Fullførte henting av alle depot-nøkler for totalt {0} apper.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Det finnes ingen nye data, alt er oppdatert.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Kunne ikke sende inn data fordi det ikke er gyldig SteamID satt som vi kan klassifisere som bidragsyter. Vurder å sette opp {0} egenskapen.</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>Innsendingen mislyktes på grunn av for mange forespørsler sendt, vi prøver igjen om lag {0} nå.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Dataene har blitt sendt inn. Serveren har registrert totalt nye applikasjoner/pakker/depoter: {0} ({1} verifisert)/{2} ({3} bekreftet)/{4} ({5} verifisert).</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>Nye apper: {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>Verifiserte apper: {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>Nye pakker: {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>Verifiserte pakker: {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>Nye depoter: {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>Verifiserte depoter: {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} initialisert, utvidelsen vil ikke løse noen av disse: {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>Laster STD global cache...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validerer STD global cache integritet...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Kan ikke verifisere STD globale cachens integritet. Dette foreslår en potensiell fil-/minnesorpsjon, et frisk eksempel vil bli initialisert i stedet.</value>
</data>
</root>

View File

@@ -107,7 +107,10 @@
<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="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Concluído {0} de {1} solicitações de chave do depot.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Recuperamos códigos de acesso para {0} aplicativos.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -68,7 +68,10 @@
<value>{0} está atualmente desativado de acordo com a sua configuração. Se quiser ajudar o SteamDB na submissão de dados, por favor confira o nosso 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 por 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>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
@@ -92,24 +95,80 @@
<value>Terminado o recolhimento de um total de {0} tokens de acesso a aplicações.</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 para um total de apps {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>Retrieving {0} app infos...</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>Informações do aplicativo {0} finalizadas.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>{0} terminou de pedidos chave de depósito {1}.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Terminou de recuperar todas as chaves de depósito para um total de aplicativos {0}.</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>
</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>
<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>Enviar um total de apps/pacotes/pacotes registrados: {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>O envio falhou devido a muitas solicitações enviadas, tentaremos novamente em aproximadamente {0} a partir de agora.</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>
<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>Novos aplicativos: {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>Apps verificados: {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>Novos pacotes: {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>Pacotes verificados: {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>Novos depósitos: {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>
<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>
<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 global STD...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validando integridade de cache global STD...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Falha ao verificar a integridade do cache global STD. Isso sugere uma potencial corrupção de arquivo/memória, uma instância nova será inicializada em vez disso.</value>
</data>
</root>

View File

@@ -107,7 +107,10 @@
<value>FINISHD RETRIEVIN {0} APP INFOS.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>FINISHD {0} OUT OV {1} DEPOT KEY REQUESTS.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>FINISHD RETRIEVIN ALL DEPOT KEYS 4 TOTAL OV {0} APPS.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -68,7 +68,10 @@
<value>{0} este momentan dezactivat în funcție de configurația ta. Dacă doriți să ajutați SteamDB în transmiterea de date, vă rugăm să consultați wiki-ul.</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 fost inițializat cu succes, îți mulțumim anticipat pentru ajutorul tău. Prima trimitere va avea loc în aproximativ {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} nu a putut fi încărcat, o nouă instanță va fi inițializată...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
@@ -104,7 +107,10 @@
<value>S-a terminat preluarea a {0} informații despre aplicații.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Am terminat {0} din {1} cereri de chei de depozit.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>S-a terminat preluarea tuturor depot keys pentru un total de {0} aplicații.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -107,7 +107,10 @@
<value>Завершено получение информации {0} приложений.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Завершено {0} из {1} запросов ключей хранилища.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -60,33 +60,115 @@
<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} har inaktiverats på grund av en saknad build-token</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} är för närvarande inaktiverat enligt din konfiguration. Om du vill hjälpa SteamDB i datainlämning, vänligen kolla in vår 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} har initierats framgångsrikt, tack i förväg för din hjälp. Den första inlämningen kommer att ske i ungefär {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} kunde inte laddas, en ny instans kommer att initieras...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Det finns inga appar som kräver en uppdatering på denna bot instans.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Hämtar totalt {0} app access-tokens...</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>Hämtar {0} app access-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>Slutförd hämtning av {0} app access-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>Slutförd hämtning av totalt {0} app access-tokens.</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>Hämtar alla depåer för totalt {0} appar...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Hämtar {0} app-infon...</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>Slutade hämta {0} app-infon.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Avslutade {0} av {1} depå nyckel förfrågningar.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Slutförd hämtning av alla depå-nycklar för totalt {0} appar.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Det finns inget ny data att skicka in, allt är uppdaterat.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Kunde inte skicka in data eftersom det inte finns någon giltig SteamID som vi kunde klassificera som en bidragsgivare. Överväg att konfigurera {0} egenskap.</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>Skicka in totalt registrerade appar/paket/depåer: {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>Inlämningen misslyckades på grund av för många förfrågningar skickade, vi försöker igen om ungefär {0} från nu.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Data har lyckats överlämnas. Servern har registrerat totalt nya appar/packet/depåer: {0} ({1} verifierad)/{2} ({3} verifierad)/{4} ({5} verifierad).</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>Nya appar: {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>Verifierade appar: {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>Nya paket: {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>Verifierade paket: {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>Nya depåer: {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>Verifierade depåer: {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} initierad, plugin kommer inte att lösa någon av dessa: {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>Laddar STD global cache...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validerar STD global cache integritet...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Det gick inte att verifiera STD global cache integritet. Detta tyder på en potentiell fil/minnes korruption, en ny instans kommer att initieras istället.</value>
</data>
</root>

View File

@@ -68,7 +68,10 @@
<value>{0} yapılandırmanıza göre şu anda devre dışı. SteamDB'ye veri gönderimi için yardımcı olmak isterseniz viki sayfamızı ziyaret edin.</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} başarıyla başlatıldı, yardımınız için şimdiden teşekkürler. İlk gönderim yaklaşık olarak {1} içinde gerçekleşecektir.</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} yüklenemedi, yeni bir örnek başlatılacak...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
@@ -104,7 +107,10 @@
<value>{0} uygulama bilgisinin alınması tamamlandı.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>{1} içinden {0} tane depo anahtar isteği tamamlandı.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Toplam {0} uygulama için tüm depo anahtarlarının alınması tamamlandı.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -68,7 +68,10 @@
<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>
@@ -104,7 +107,10 @@
<value>Завершено отримання {0} інформації про програму.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Завершено {0} з {1} запитів зі сховища.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</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>

View File

@@ -107,7 +107,10 @@
<value>Đã hoàn tất thu nhận thông tin của {0} ứng dụng.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Đã hoàn thành {0} trong tổng số {1} yêu cầu mã khóa kho.</value>
<comment>{0} will be replaced by the number (count this batch) of depot key requests that were successfully answered, {1} will be replaced by the number (count this batch) of depot key requests that were supposed to be sent</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Đã hoàn tất thu nhận tất cả khóa kho của tổng số {0} ứng dụng.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>

View File

@@ -25,7 +25,6 @@ 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

View File

@@ -360,42 +360,26 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingTotalAppAccessTokens(appIDsToRefresh.Count));
HashSet<uint> appIDsThisRound = new(Math.Min(appIDsToRefresh.Count, SharedInfo.AppInfosPerSingleRequest));
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
while (true) {
if (!bot.IsConnectedAndLoggedOn) {
return;
}
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
appIDsThisRound.Add(enumerator.Current);
}
if (appIDsThisRound.Count == 0) {
break;
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingAppAccessTokens(appIDsThisRound.Count));
SteamApps.PICSTokensCallback response;
try {
response = await bot.SteamApps.PICSGetAccessTokens(appIDsThisRound, []).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
appIDsThisRound.Clear();
continue;
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingAppAccessTokens(appIDsThisRound.Count));
appIDsThisRound.Clear();
GlobalCache.UpdateAppTokens(response.AppTokens, response.AppTokensDenied);
foreach (uint[] appIDsThisRound in appIDsToRefresh.Chunk(Bot.EntriesPerSinglePICSRequest)) {
if (!bot.IsConnectedAndLoggedOn) {
return;
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingAppAccessTokens(appIDsThisRound.Length));
SteamApps.PICSTokensCallback response;
try {
response = await bot.SteamApps.PICSGetAccessTokens(appIDsThisRound, []).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
continue;
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingAppAccessTokens(appIDsThisRound.Length));
GlobalCache.UpdateAppTokens(response.AppTokens, response.AppTokensDenied);
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingTotalAppAccessTokens(appIDsToRefresh.Count));
@@ -403,131 +387,113 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
(_, FrozenSet<uint>? knownDepotIDs) = await GlobalCache.KnownDepotIDs.GetValue(ECacheFallback.SuccessPreviously).ConfigureAwait(false);
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
while (true) {
if (!bot.IsConnectedAndLoggedOn) {
return;
}
foreach (uint[] appIDsThisRound in appIDsToRefresh.Chunk(Bot.EntriesPerSinglePICSRequest)) {
if (!bot.IsConnectedAndLoggedOn) {
return;
}
while ((appIDsThisRound.Count < SharedInfo.AppInfosPerSingleRequest) && enumerator.MoveNext()) {
appIDsThisRound.Add(enumerator.Current);
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingAppInfos(appIDsThisRound.Length));
if (appIDsThisRound.Count == 0) {
break;
}
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet response;
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotRetrievingAppInfos(appIDsThisRound.Count));
try {
response = await bot.SteamApps.PICSGetProductInfo(appIDsThisRound.Select(static appID => new SteamApps.PICSRequest(appID, GlobalCache.GetAppToken(appID))), []).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet response;
continue;
}
try {
response = await bot.SteamApps.PICSGetProductInfo(appIDsThisRound.Select(static appID => new SteamApps.PICSRequest(appID, GlobalCache.GetAppToken(appID))), []).ToLongRunningTask().ConfigureAwait(false);
} catch (Exception e) {
bot.ArchiLogger.LogGenericWarningException(e);
if (response.Results == null) {
bot.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.FormatWarningFailedWithError(nameof(response.Results)));
appIDsThisRound.Clear();
continue;
}
continue;
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingAppInfos(appIDsThisRound.Length));
if (response.Results == null) {
bot.ArchiLogger.LogGenericWarning(ArchiSteamFarm.Localization.Strings.FormatWarningFailedWithError(nameof(response.Results)));
Dictionary<uint, uint> appChangeNumbers = new();
appIDsThisRound.Clear();
uint depotKeysSuccessful = 0;
uint depotKeysTotal = 0;
continue;
}
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo app in response.Results.SelectMany(static result => result.Apps.Values)) {
appChangeNumbers[app.ID] = app.ChangeNumber;
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingAppInfos(appIDsThisRound.Count));
bool shouldFetchMainKey = false;
appIDsThisRound.Clear();
foreach (KeyValue depot in app.KeyValues["depots"].Children) {
if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || (Config?.SecretDepotIDs.Contains(depotID) == true) || !GlobalCache.ShouldRefreshDepotKey(depotID)) {
continue;
}
Dictionary<uint, uint> appChangeNumbers = new();
depotKeysTotal++;
uint depotKeysSuccessful = 0;
uint depotKeysTotal = 0;
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
foreach (SteamApps.PICSProductInfoCallback.PICSProductInfo app in response.Results.SelectMany(static result => result.Apps.Values)) {
appChangeNumbers[app.ID] = app.ChangeNumber;
try {
SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(depotID, app.ID).ToLongRunningTask().ConfigureAwait(false);
bool shouldFetchMainKey = false;
depotKeysSuccessful++;
foreach (KeyValue depot in app.KeyValues["depots"].Children) {
if (!uint.TryParse(depot.Name, out uint depotID) || (knownDepotIDs?.Contains(depotID) == true) || (Config?.SecretDepotIDs.Contains(depotID) == true) || !GlobalCache.ShouldRefreshDepotKey(depotID)) {
if (depotResponse.Result != EResult.OK) {
continue;
}
depotKeysTotal++;
shouldFetchMainKey = true;
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
GlobalCache.UpdateDepotKey(depotResponse);
} catch (Exception e) {
// We can still try other depots
bot.ArchiLogger.LogGenericWarningException(e);
} finally {
Utilities.InBackground(async () => {
await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false);
try {
SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(depotID, app.ID).ToLongRunningTask().ConfigureAwait(false);
depotKeysSuccessful++;
if (depotResponse.Result != EResult.OK) {
continue;
// ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it
depotsRateLimitingSemaphore.Release();
}
shouldFetchMainKey = true;
GlobalCache.UpdateDepotKey(depotResponse);
} catch (Exception e) {
// We can still try other depots
bot.ArchiLogger.LogGenericWarningException(e);
} finally {
Utilities.InBackground(
async () => {
await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false);
// ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it
depotsRateLimitingSemaphore.Release();
}
);
}
);
}
}
// Consider fetching main appID key only if we've actually considered some new depots for resolving
if (shouldFetchMainKey && (knownDepotIDs?.Contains(app.ID) != true) && GlobalCache.ShouldRefreshDepotKey(app.ID)) {
// Consider fetching main appID key only if we've actually considered some new depots for resolving
if (shouldFetchMainKey && (knownDepotIDs?.Contains(app.ID) != true) && GlobalCache.ShouldRefreshDepotKey(app.ID)) {
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
try {
SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(app.ID, app.ID).ToLongRunningTask().ConfigureAwait(false);
// Increment total in combination with successful, we allow this one to fail on us
depotKeysTotal++;
depotKeysSuccessful++;
await depotsRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
GlobalCache.UpdateDepotKey(depotResponse);
} catch (Exception e) {
// We can still try other depots
bot.ArchiLogger.LogGenericWarningException(e);
} finally {
Utilities.InBackground(async () => {
await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false);
try {
SteamApps.DepotKeyCallback depotResponse = await bot.SteamApps.GetDepotDecryptionKey(app.ID, app.ID).ToLongRunningTask().ConfigureAwait(false);
depotKeysSuccessful++;
GlobalCache.UpdateDepotKey(depotResponse);
} catch (Exception e) {
// We can still try other depots
bot.ArchiLogger.LogGenericWarningException(e);
} finally {
Utilities.InBackground(
async () => {
await Task.Delay(DepotsRateLimitingDelay).ConfigureAwait(false);
// ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it
depotsRateLimitingSemaphore.Release();
}
);
}
// ReSharper disable once AccessToDisposedClosure - we're waiting for the semaphore to be free before disposing it
depotsRateLimitingSemaphore.Release();
}
);
}
}
if (depotKeysTotal > 0) {
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingDepotKeys(depotKeysSuccessful, depotKeysTotal));
}
if (depotKeysSuccessful < depotKeysTotal) {
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
continue;
}
GlobalCache.UpdateAppChangeNumbers(appChangeNumbers);
}
if (depotKeysTotal > 0) {
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingDepotKeys(depotKeysSuccessful, depotKeysTotal));
}
if (depotKeysSuccessful < depotKeysTotal) {
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
continue;
}
GlobalCache.UpdateAppChangeNumbers(appChangeNumbers);
}
bot.ArchiLogger.LogGenericInfo(Strings.FormatBotFinishedRetrievingTotalDepots(appIDsToRefresh.Count));
@@ -561,8 +527,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
return bot.Commands.FormatBotResponse(ArchiSteamFarm.Localization.Strings.FormatWarningFailedWithError(nameof(GlobalCache)));
}
Utilities.InBackground(
async () => {
Utilities.InBackground(async () => {
await Refresh(bot).ConfigureAwait(false);
await SubmitData().ConfigureAwait(false);
}
@@ -598,8 +563,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
return Commands.FormatStaticResponse(ArchiSteamFarm.Localization.Strings.FormatWarningFailedWithError(nameof(GlobalCache)));
}
Utilities.InBackground(
async () => {
Utilities.InBackground(async () => {
await Utilities.InParallel(bots.Select(static bot => Refresh(bot))).ConfigureAwait(false);
await SubmitData().ConfigureAwait(false);

View File

@@ -36,7 +36,7 @@ internal sealed class ArchiCryptoHelper {
[DataRow(ECryptoMethod.PlainText)]
[DataRow(ECryptoMethod.AES)]
[DataTestMethod]
[TestMethod]
internal async Task CanEncryptDecrypt(ECryptoMethod cryptoMethod) {
if (!Enum.IsDefined(cryptoMethod)) {
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="MSTest" />
</ItemGroup>

View File

@@ -26,7 +26,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers.Json;
@@ -50,7 +49,7 @@ internal sealed class Bot {
throw new InvalidOperationException(nameof(constructor));
}
JsonElement emptyObject = new JsonObject().ToJsonElement();
JsonObject emptyObject = new();
BotConfig? botConfig = emptyObject.ToJsonObject<BotConfig>();
@@ -106,7 +105,7 @@ internal sealed class Bot {
CreateCard(2, realAppID: appID)
];
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
}
[TestMethod]
@@ -472,7 +471,7 @@ internal sealed class Bot {
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
Assert.IsLessThanOrEqualTo(Steam.Exchange.Trading.MaxItemsPerTrade, itemsToSend.Count);
}
[TestMethod]
@@ -496,7 +495,7 @@ internal sealed class Bot {
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
Assert.IsLessThanOrEqualTo(Steam.Exchange.Trading.MaxItemsPerTrade, itemsToSend.Count);
}
[TestMethod]
@@ -512,8 +511,7 @@ internal sealed class Bot {
CreateCard(4, realAppID: appID0)
];
Assert.ThrowsException<InvalidOperationException>(
() => GetItemsForFullBadge(
Assert.ThrowsExactly<InvalidOperationException>(() => GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 3 },
{ appID1, 3 },

View File

@@ -0,0 +1,96 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.NLog;
using ArchiSteamFarm.Storage;
using ArchiSteamFarm.Web;
using ArchiSteamFarm.Web.GitHub;
using ArchiSteamFarm.Web.GitHub.Data;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ArchiSteamFarm.Tests;
#pragma warning disable CA1812 // False positive, the class is used during MSTest
[TestClass]
internal sealed class IGitHubPluginUpdates {
private const string PluginName = "ArchiSteamFarm.OfficialPlugins.Monitoring";
private const string Repository = "JustArchiNET/ArchiSteamFarm";
private readonly TestContext TestContext;
private CancellationToken CancellationToken => TestContext.CancellationTokenSource.Token;
[UsedImplicitly]
public IGitHubPluginUpdates(TestContext testContext) {
ArgumentNullException.ThrowIfNull(testContext);
TestContext = testContext;
}
[TestCategory("Manual")]
[TestMethod]
internal async Task DoesNotOfferPointlessUpdatesWhenMultipleAssetsAreFound() {
using WebBrowser webBrowser = new(new ArchiLogger("Test"));
typeof(ASF).GetProperty(nameof(ASF.WebBrowser))?.SetValue(null, webBrowser);
ReleaseResponse? response = await GitHubService.GetLatestRelease(Repository, cancellationToken: CancellationToken).ConfigureAwait(false);
if (response == null) {
Assert.Inconclusive(Strings.FormatWarningFailedWithError(nameof(response)));
}
Version version = Version.Parse(response.Tag);
Plugins.Interfaces.IGitHubPluginUpdates plugin = new TestGitHubPluginUpdates(version);
Uri? releaseURL = await plugin.GetTargetReleaseURL(version, BuildInfo.Variant, true, GlobalConfig.EUpdateChannel.Stable, false).ConfigureAwait(false);
Assert.IsNull(releaseURL);
Uri? forcedReleaseURL = await plugin.GetTargetReleaseURL(version, BuildInfo.Variant, true, GlobalConfig.EUpdateChannel.Stable, true).ConfigureAwait(false);
Assert.IsNotNull(forcedReleaseURL);
}
private sealed class TestGitHubPluginUpdates : Plugins.Interfaces.IGitHubPluginUpdates {
public string Name => PluginName;
public string RepositoryName => Repository;
public Version Version { get; }
internal TestGitHubPluginUpdates(Version version) {
ArgumentNullException.ThrowIfNull(version);
Version = version;
}
public Task OnLoaded() => Task.CompletedTask;
}
}
#pragma warning restore CA1812 // False positive, the class is used during MSTest

View File

@@ -36,7 +36,7 @@ internal sealed class MobileAuthenticator {
[DataRow("qrg+wW8/u/TDt2i/+FQuPhuVrmY=", (ulong) 1337, "mYbCKs8ZvsVN2odCMxpvidrIu1c=", "conf")]
[DataRow("qrg+wW8/u/TDt2i/+FQuPhuVrmY=", (ulong) 1723332288, "hiEx+JBqJqFJnSSL+dEthPHOmsc=")]
[DataRow("qrg+wW8/u/TDt2i/+FQuPhuVrmY=", (ulong) 1723332288, "hpZUxyNgwBvtKPROvedjuvVPQiE=", "conf")]
[DataTestMethod]
[TestMethod]
internal void GenerateConfirmationHash(string identitySecret, ulong time, string expectedCode, string? tag = null) {
ArgumentException.ThrowIfNullOrEmpty(identitySecret);
ArgumentOutOfRangeException.ThrowIfZero(time);
@@ -58,7 +58,7 @@ internal sealed class MobileAuthenticator {
[DataRow("KDHC3rsY8+CmiswnXJcE5e5dRfd=", (ulong) 1337, "47J4D")]
[DataRow("KDHC3rsY8+CmiswnXJcE5e5dRfd=", (ulong) 1723332288, "JQ3HQ")]
[DataTestMethod]
[TestMethod]
internal void GenerateTokenForTime(string sharedSecret, ulong time, string expectedCode) {
ArgumentException.ThrowIfNullOrEmpty(sharedSecret);
ArgumentOutOfRangeException.ThrowIfZero(time);
@@ -81,7 +81,7 @@ internal sealed class MobileAuthenticator {
["shared_secret"] = sharedSecret
};
Steam.Security.MobileAuthenticator? result = jsonObject.ToJsonElement().ToJsonObject<Steam.Security.MobileAuthenticator>();
Steam.Security.MobileAuthenticator? result = jsonObject.ToJsonObject<Steam.Security.MobileAuthenticator>();
if (result == null) {
throw new InvalidOperationException(nameof(result));

View File

@@ -25,7 +25,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static ArchiSteamFarm.Steam.Integration.SteamChatMessage;
@@ -34,6 +36,17 @@ namespace ArchiSteamFarm.Tests;
#pragma warning disable CA1812 // False positive, the class is used during MSTest
[TestClass]
internal sealed class SteamChatMessage {
private readonly TestContext TestContext;
private CancellationToken CancellationToken => TestContext.CancellationTokenSource.Token;
[UsedImplicitly]
public SteamChatMessage(TestContext testContext) {
ArgumentNullException.ThrowIfNull(testContext);
TestContext = testContext;
}
[TestMethod]
internal async Task CanSplitEvenWithStupidlyLongPrefix() {
string prefix = new('x', MaxMessagePrefixBytes);
@@ -41,9 +54,9 @@ internal sealed class SteamChatMessage {
const string emoji = "😎";
const string message = $"{emoji}{emoji}{emoji}{emoji}";
List<string> output = await GetMessageParts(message, prefix, true).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, prefix, true).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(4, output.Count);
Assert.HasCount(4, output);
Assert.AreEqual($"{prefix}{emoji}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}{ContinuationCharacter}", output[1]);
@@ -58,15 +71,15 @@ internal sealed class SteamChatMessage {
internal async Task DoesntSkipEmptyNewlines() {
string message = $"asdf{Environment.NewLine}{Environment.NewLine}asdf";
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual(message, output.First());
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
[TestMethod]
internal async Task DoesntSplitInTheMiddleOfMultiByteChar(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
@@ -76,9 +89,9 @@ internal sealed class SteamChatMessage {
string longSequence = new('a', longLineLength - 1);
string message = $"{longSequence}{emoji}";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(2, output.Count);
Assert.HasCount(2, output);
Assert.AreEqual($"{longSequence}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{ContinuationCharacter}{emoji}", output[1]);
@@ -89,15 +102,15 @@ internal sealed class SteamChatMessage {
const string message = "abcdef[";
const string escapedMessage = @"abcdef\[";
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual(escapedMessage, output.First());
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
[TestMethod]
internal async Task DoesntSplitOnBackslashNotUsedForEscaping(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
@@ -105,15 +118,15 @@ internal sealed class SteamChatMessage {
string longLine = new('a', longLineLength - 2);
string message = $@"{longLine}\";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual($@"{message}\", output.First());
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
[TestMethod]
internal async Task DoesntSplitOnEscapeCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
@@ -121,9 +134,9 @@ internal sealed class SteamChatMessage {
string longLine = new('a', longLineLength - 1);
string message = $"{longLine}[";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(2, output.Count);
Assert.HasCount(2, output);
Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
Assert.AreEqual($@"{ContinuationCharacter}\[", output[1]);
@@ -133,9 +146,9 @@ internal sealed class SteamChatMessage {
internal async Task NoNeedForAnySplittingWithNewlines() {
string message = $"abcdef{Environment.NewLine}ghijkl{Environment.NewLine}mnopqr";
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual(message, output.First());
}
@@ -143,23 +156,23 @@ internal sealed class SteamChatMessage {
internal async Task NoNeedForAnySplittingWithoutNewlines() {
const string message = "abcdef";
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual(message, output.First());
}
[TestMethod]
internal void ParagraphCharacterSizeIsLessOrEqualToContinuationCharacterSize() => Assert.IsTrue(ContinuationCharacterBytes >= Encoding.UTF8.GetByteCount(ParagraphCharacter.ToString()));
internal void ParagraphCharacterSizeIsLessOrEqualToContinuationCharacterSize() => Assert.IsGreaterThanOrEqualTo(Encoding.UTF8.GetByteCount(ParagraphCharacter.ToString()), ContinuationCharacterBytes);
[TestMethod]
internal async Task ProperlyEscapesCharacters() {
const string message = @"[b]bold[/b] \n";
const string escapedMessage = @"\[b]bold\[/b] \\n";
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual(escapedMessage, output.First());
}
@@ -170,15 +183,15 @@ internal sealed class SteamChatMessage {
const string message = "asdf";
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, prefix).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(1, output.Count);
Assert.HasCount(1, output);
Assert.AreEqual($"{escapedPrefix}{message}", output.First());
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
[TestMethod]
internal async Task ProperlySplitsLongSingleLine(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
@@ -186,9 +199,9 @@ internal sealed class SteamChatMessage {
string longLine = new('a', longLineLength);
string message = $"{longLine}{longLine}{longLine}{longLine}";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(4, output.Count);
Assert.HasCount(4, output);
Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{ContinuationCharacter}{longLine}{ContinuationCharacter}", output[1]);
@@ -260,9 +273,9 @@ internal sealed class SteamChatMessage {
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.
""";
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, prefix).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(2, output.Count);
Assert.HasCount(2, output);
foreach (string messagePart in output) {
if ((messagePart.Length <= prefix.Length) || !messagePart.StartsWith(prefix, StringComparison.Ordinal)) {
@@ -285,7 +298,7 @@ internal sealed class SteamChatMessage {
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
[TestMethod]
internal async Task SplitsOnNewlinesWithParagraphCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
@@ -304,9 +317,9 @@ internal sealed class SteamChatMessage {
string newlinePart = newlinePartBuilder.ToString();
string message = $"{newlinePart}{Environment.NewLine}{newlinePart}{Environment.NewLine}{newlinePart}{Environment.NewLine}{newlinePart}";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync(CancellationToken).ConfigureAwait(false);
Assert.AreEqual(4, output.Count);
Assert.HasCount(4, output);
Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[0]);
Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[1]);
@@ -320,7 +333,7 @@ internal sealed class SteamChatMessage {
const string message = "asdf";
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
await Assert.ThrowsExactlyAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync(CancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
}
[TestMethod]
@@ -329,7 +342,7 @@ internal sealed class SteamChatMessage {
const string message = "asdf";
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
await Assert.ThrowsExactlyAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync(CancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
}
}
#pragma warning restore CA1812 // False positive, the class is used during MSTest

View File

@@ -187,6 +187,8 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToExpressionBodyWhenPossible/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpressionWhenPossible/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DuplicatedChainedIfBodies/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DuplicatedSequentialIfBodies/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DuplicatedSwitchSectionBodies/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=DuplicateResource/@EntryIndexedValue">DO_NOT_SHOW</s:String>
@@ -256,6 +258,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PatternAlwaysMatches/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PatternAlwaysOfType/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PlaceAssignmentExpressionIntoBlock/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PreferExplicitlyProvidedTupleComponentName/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PrimaryConstructorParameterCaptureDisallowed/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PropertyNotResolved/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RawStringCanBeSimplified/@EntryIndexedValue">SUGGESTION</s:String>

View File

@@ -5,18 +5,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp.XPath" />
<PackageReference Include="AngleSharp" />
<PackageReference Include="CryptSharpStandard" />
<PackageReference Include="Humanizer" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations.Sources" PrivateAssets="all" />
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" PrivateAssets="all" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="Nito.AsyncEx.Coordination" />
<PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="SteamKit2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" />
<PackageReference Include="System.Composition" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" />

View File

@@ -100,9 +100,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
public void ExceptWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
foreach (T item in other) {
Remove(item);
}
RemoveRange(other);
}
[MustDisposeResource]
@@ -113,8 +111,14 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
IReadOnlySet<T> otherSet = other as IReadOnlySet<T> ?? other.ToHashSet();
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
Remove(item);
bool modified = false;
foreach (T _ in this.Where(item => !otherSet.Contains(item) && BackingCollection.TryRemove(item, out _))) {
modified = true;
}
if (modified) {
OnModified?.Invoke(this, EventArgs.Empty);
}
}
@@ -182,24 +186,24 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
ArgumentNullException.ThrowIfNull(other);
IReadOnlySet<T> otherSet = other as IReadOnlySet<T> ?? other.ToHashSet();
HashSet<T> removed = [];
foreach (T item in otherSet.Where(Contains)) {
removed.Add(item);
Remove(item);
HashSet<T> removed = otherSet.Where(item => BackingCollection.TryRemove(item, out _)).ToHashSet();
bool modified = removed.Count > 0;
foreach (T _ in otherSet.Where(item => !removed.Contains(item) && BackingCollection.TryAdd(item, true))) {
modified = true;
}
foreach (T item in otherSet.Where(item => !removed.Contains(item))) {
Add(item);
if (modified) {
OnModified?.Invoke(this, EventArgs.Empty);
}
}
public void UnionWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
foreach (T otherElement in other) {
Add(otherElement);
}
AddRange(other);
}
void ICollection<T>.Add(T item) {
@@ -215,44 +219,66 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
public bool AddRange(IEnumerable<T> items) {
ArgumentNullException.ThrowIfNull(items);
bool result = false;
bool modified = false;
foreach (T _ in items.Where(Add)) {
result = true;
foreach (T _ in items.Where(item => BackingCollection.TryAdd(item, true))) {
modified = true;
}
return result;
if (modified) {
OnModified?.Invoke(this, EventArgs.Empty);
}
return modified;
}
[PublicAPI]
public bool RemoveRange(IEnumerable<T> items) {
ArgumentNullException.ThrowIfNull(items);
bool result = false;
bool modified = false;
foreach (T _ in items.Where(Remove)) {
result = true;
foreach (T _ in items.Where(item => BackingCollection.TryRemove(item, out _))) {
modified = true;
}
return result;
if (modified) {
OnModified?.Invoke(this, EventArgs.Empty);
}
return modified;
}
[PublicAPI]
public int RemoveWhere(Predicate<T> match) {
ArgumentNullException.ThrowIfNull(match);
return BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
int count = BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
if (count > 0) {
OnModified?.Invoke(this, EventArgs.Empty);
}
return count;
}
[PublicAPI]
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
public bool ReplaceIfNeededWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
if (SetEquals(other)) {
ICollection<T> otherCollection = other as ICollection<T> ?? other.ToHashSet();
if (SetEquals(otherCollection)) {
return false;
}
ReplaceWith(other);
BackingCollection.Clear();
foreach (T item in otherCollection) {
BackingCollection.TryAdd(item, true);
}
OnModified?.Invoke(this, EventArgs.Empty);
return true;
}
@@ -261,7 +287,6 @@ public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : no
public void ReplaceWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
Clear();
UnionWith(other);
ReplaceIfNeededWith(other);
}
}

View File

@@ -24,6 +24,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Nito.AsyncEx;
@@ -60,6 +61,12 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
ArgumentOutOfRangeException.ThrowIfNegative(index);
using (Lock.WriterLock()) {
T oldValue = BackingCollection[index];
if (EqualityComparer<T>.Default.Equals(oldValue, value)) {
return;
}
BackingCollection[index] = value;
}
@@ -86,6 +93,10 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
public void Clear() {
using (Lock.WriterLock()) {
if (BackingCollection.Count == 0) {
return;
}
BackingCollection.Clear();
}
@@ -155,9 +166,15 @@ public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
public void ReplaceWith(IEnumerable<T> collection) {
ArgumentNullException.ThrowIfNull(collection);
ICollection<T> newCollection = collection as ICollection<T> ?? collection.ToList();
using (Lock.WriterLock()) {
if (BackingCollection.SequenceEqual(newCollection)) {
return;
}
BackingCollection.Clear();
BackingCollection.AddRange(collection);
BackingCollection.AddRange(newCollection);
}
OnModified?.Invoke(this, EventArgs.Empty);

View File

@@ -33,19 +33,17 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> where T : not
private readonly ConcurrentQueue<T> BackingQueue = new();
internal byte MaxCount {
get => BackingMaxCount;
get;
set {
ArgumentOutOfRangeException.ThrowIfZero(value);
BackingMaxCount = value;
field = value;
Resize();
}
}
private byte BackingMaxCount;
internal FixedSizeConcurrentQueue(byte maxCount) {
ArgumentOutOfRangeException.ThrowIfZero(maxCount);

View File

@@ -65,6 +65,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
}
BackingDictionary[key] = value;
OnModified?.Invoke(this, EventArgs.Empty);
}
}
@@ -111,6 +112,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
}
BackingDictionary.Clear();
OnModified?.Invoke(this, EventArgs.Empty);
}

View File

@@ -83,7 +83,7 @@ public static class ASF {
internal static ICrossProcessSemaphore? RateLimitingSemaphore { get; private set; }
internal static FrozenDictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
private static readonly FrozenSet<string> AssembliesNeededBeforeUpdate = new HashSet<string>(1, StringComparer.Ordinal) { "System.IO.Pipes" }.ToFrozenSet(StringComparer.Ordinal);
private static readonly FrozenSet<string> AssembliesNeededBeforeUpdate = FrozenSet.Create(StringComparer.Ordinal, "System.IO.Pipes");
private static readonly SemaphoreSlim UpdateSemaphore = new(1, 1);
private static Timer? AutoUpdatesTimer;
@@ -355,8 +355,8 @@ public static class ASF {
}
ArchiLogger.LogGenericInfo(Strings.IPCConfigChanged);
await ArchiKestrel.Stop().ConfigureAwait(false);
await ArchiKestrel.Start().ConfigureAwait(false);
await ArchiKestrel.Restart().ConfigureAwait(false);
}
private static async Task OnChangedFile(string name, string fullPath) {
@@ -773,13 +773,7 @@ public static class ASF {
ReleaseResponse? releaseResponse = await GitHubService.GetLatestRelease(SharedInfo.GithubRepo, channel == GlobalConfig.EUpdateChannel.Stable).ConfigureAwait(false);
if (releaseResponse == null) {
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
return (false, null);
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
if ((releaseResponse == null) || string.IsNullOrEmpty(releaseResponse.Tag)) {
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
return (false, null);
@@ -849,8 +843,7 @@ public static class ASF {
BinaryResponse? response;
try {
// ReSharper disable once MethodSupportsCancellation - the token initialized above is not meant to be passed here
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL, progressReporter: progressReporter).ConfigureAwait(false);
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL, progressReporter: progressReporter, cancellationToken: CancellationToken.None).ConfigureAwait(false);
} finally {
progressReporter.ProgressChanged -= onProgressChanged;
}

View File

@@ -114,7 +114,7 @@ internal static class ArchiNet {
return null;
}
IAttr? paramsNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='openidparams']/@value");
IElement? paramsNode = challengeResponse.Content.QuerySelector("input[name='openidparams'][value]");
if (paramsNode == null) {
ASF.ArchiLogger.LogNullError(paramsNode);
@@ -122,7 +122,7 @@ internal static class ArchiNet {
return null;
}
string paramsValue = paramsNode.Value;
string? paramsValue = paramsNode.GetAttribute("value");
if (string.IsNullOrEmpty(paramsValue)) {
ASF.ArchiLogger.LogNullError(paramsValue);
@@ -130,7 +130,7 @@ internal static class ArchiNet {
return null;
}
IAttr? nonceNode = challengeResponse.Content.SelectSingleNode<IAttr>("//input[@name='nonce']/@value");
IElement? nonceNode = challengeResponse.Content.QuerySelector("input[name='nonce'][value]");
if (nonceNode == null) {
ASF.ArchiLogger.LogNullError(nonceNode);
@@ -138,7 +138,7 @@ internal static class ArchiNet {
return null;
}
string nonceValue = nonceNode.Value;
string? nonceValue = nonceNode.GetAttribute("value");
if (string.IsNullOrEmpty(nonceValue)) {
ASF.ArchiLogger.LogNullError(nonceValue);
@@ -183,7 +183,7 @@ internal static class ArchiNet {
private static async Task<(bool Success, IReadOnlyCollection<ulong>? Result)> ResolveCachedBadBots(CancellationToken cancellationToken = default) {
if (ASF.GlobalDatabase == null) {
throw new InvalidOperationException(nameof(ASF.WebBrowser));
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
}
if (ASF.WebBrowser == null) {

View File

@@ -24,6 +24,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -56,19 +57,18 @@ internal static class OS {
internal static string? Runtime => TrimAndNullifyEmptyText(RuntimeInformation.RuntimeIdentifier);
[field: AllowNull]
[field: MaybeNull]
internal static string Version {
get {
if (!string.IsNullOrEmpty(BackingVersion)) {
return BackingVersion;
if (!string.IsNullOrEmpty(field)) {
return field;
}
BackingVersion = $"{Framework ?? "Unknown Framework"}; {Runtime ?? "Unknown Runtime"}; {Description ?? "Unknown OS"}";
return BackingVersion;
return field = $"{Framework ?? "Unknown Framework"}; {Runtime ?? "Unknown Runtime"}; {Description ?? "Unknown OS"}";
}
}
private static string? BackingVersion;
private static Mutex? SingleInstance;
internal static void CoreInit(bool minimized, bool systemRequired) {
@@ -304,6 +304,11 @@ internal static class OS {
return;
}
if (!consoleMode.HasFlag(NativeMethods.EConsoleMode.EnableQuickEditMode)) {
// Quick edit mode already disabled
return;
}
consoleMode &= ~NativeMethods.EConsoleMode.EnableQuickEditMode;
if (!NativeMethods.SetConsoleMode(consoleHandle, consoleMode)) {

View File

@@ -35,8 +35,6 @@ using System.Resources;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Dom;
using AngleSharp.XPath;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.NLog;
using ArchiSteamFarm.Storage;
@@ -53,7 +51,7 @@ public static class Utilities {
private const byte TimeoutForLongRunningTasksInSeconds = 60;
private const uint UnauthorizedAccessHResult = 0x80070005;
private static readonly FrozenSet<char> DirectorySeparators = new HashSet<char>(2) { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }.ToFrozenSet();
private static readonly FrozenSet<char> DirectorySeparators = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar];
[PublicAPI]
public static IEnumerable<T> AsLinqThreadSafeEnumerable<T>(this ICollection<T> collection) {
@@ -63,6 +61,13 @@ public static class Utilities {
return collection.Select(static entry => entry);
}
[PublicAPI]
public static string AsMasked(this string text, char mask = '*') {
ArgumentNullException.ThrowIfNull(text);
return new string(mask, text.Length);
}
[PublicAPI]
public static string GenerateChecksumFor(byte[] source) {
ArgumentNullException.ThrowIfNull(source);
@@ -190,48 +195,6 @@ public static class Utilities {
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
}
[PublicAPI]
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
ArgumentNullException.ThrowIfNull(document);
return document.Body.SelectNodes(xpath);
}
[PublicAPI]
public static IEnumerable<T> SelectNodes<T>(this IDocument document, string xpath) where T : class, INode {
ArgumentNullException.ThrowIfNull(document);
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]
public static IEnumerable<T> ToEnumerable<T>(this T item) {
yield return item;

View File

@@ -28,6 +28,7 @@ using System.Security.AccessControl;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Localization;
namespace ArchiSteamFarm.Helpers;
@@ -43,8 +44,6 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
ArgumentException.ThrowIfNullOrEmpty(name);
FilePath = Path.Combine(Path.GetTempPath(), SharedInfo.ASF, name);
EnsureFileExists();
}
public void Dispose() {
@@ -86,6 +85,10 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
try {
while (true) {
if (!await EnsureFileExists().ConfigureAwait(false)) {
throw new IOException(Strings.FormatWarningFailedWithError(nameof(EnsureFileExists)));
}
try {
// 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) {
@@ -93,13 +96,13 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
throw new InvalidOperationException(nameof(FileLock));
}
EnsureFileExists();
FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
FileLock = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None);
success = true;
return;
}
} catch (FileNotFoundException) {
throw;
} catch (IOException) {
await Task.Delay(SpinLockDelay, cancellationToken).ConfigureAwait(false);
}
@@ -132,6 +135,10 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
while (true) {
if (!await EnsureFileExists().ConfigureAwait(false)) {
throw new IOException(Strings.FormatWarningFailedWithError(nameof(EnsureFileExists)));
}
try {
// 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) {
@@ -139,13 +146,13 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
throw new InvalidOperationException(nameof(FileLock));
}
EnsureFileExists();
FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
FileLock = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None);
success = true;
return true;
}
} catch (FileNotFoundException) {
throw;
} catch (IOException) {
if (millisecondsTimeout <= SpinLockDelay) {
return false;
@@ -162,55 +169,70 @@ internal sealed class CrossProcessFileBasedSemaphore : IAsyncDisposable, ICrossP
}
}
private void EnsureFileExists() {
if (File.Exists(FilePath)) {
return;
}
private async Task<bool> EnsureFileExists() {
for (byte i = 0; i < 2; i++) {
if (File.Exists(FilePath)) {
return true;
}
string? directoryPath = Path.GetDirectoryName(FilePath);
string? directoryPath = Path.GetDirectoryName(FilePath);
if (string.IsNullOrEmpty(directoryPath)) {
ASF.ArchiLogger.LogNullError(directoryPath);
if (string.IsNullOrEmpty(directoryPath)) {
ASF.ArchiLogger.LogNullError(directoryPath);
return;
}
return false;
}
if (!Directory.Exists(directoryPath)) {
DirectoryInfo directoryInfo = Directory.CreateDirectory(directoryPath);
if (!Directory.Exists(directoryPath)) {
if (OperatingSystem.IsWindows()) {
DirectoryInfo directoryInfo = Directory.CreateDirectory(directoryPath);
if (OperatingSystem.IsWindows()) {
try {
DirectorySecurity directorySecurity = new(directoryPath, AccessControlSections.All);
try {
DirectorySecurity directorySecurity = new(directoryPath, AccessControlSections.All);
directoryInfo.SetAccessControl(directorySecurity);
} catch (PrivilegeNotHeldException e) {
// Non-critical, user might have no rights to manage the resource
ASF.ArchiLogger.LogGenericDebuggingException(e);
directoryInfo.SetAccessControl(directorySecurity);
} catch (PrivilegeNotHeldException e) {
// Non-critical, user might have no rights to manage the resource
ASF.ArchiLogger.LogGenericDebuggingException(e);
}
} else if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) {
// We require global access from all users, as other ASFs might need to put additional files in there
Directory.CreateDirectory(directoryPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute);
}
} else if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) {
directoryInfo.UnixFileMode |= UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
}
FileStreamOptions fileStreamOptions = new() {
Mode = FileMode.CreateNew,
Access = FileAccess.Write,
Share = FileShare.None
};
try {
if (OperatingSystem.IsWindows()) {
await new FileStream(FilePath, fileStreamOptions).DisposeAsync().ConfigureAwait(false);
FileInfo fileInfo = new(FilePath);
try {
FileSecurity fileSecurity = new(FilePath, AccessControlSections.All);
fileInfo.SetAccessControl(fileSecurity);
} catch (PrivilegeNotHeldException e) {
// Non-critical, user might have no rights to manage the resource
ASF.ArchiLogger.LogGenericDebuggingException(e);
}
} else if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) {
// Since we only create and read the files, we don't need write/execute permissions on them from other instances
fileStreamOptions.UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.OtherRead;
await new FileStream(FilePath, fileStreamOptions).DisposeAsync().ConfigureAwait(false);
}
} catch (IOException e) {
// Ignored, if the file was already created in the meantime by another instance, this is fine
ASF.ArchiLogger.LogGenericDebuggingException(e);
}
}
try {
new FileStream(FilePath, FileMode.CreateNew).Dispose();
FileInfo fileInfo = new(FilePath);
if (OperatingSystem.IsWindows()) {
try {
FileSecurity fileSecurity = new(FilePath, AccessControlSections.All);
fileInfo.SetAccessControl(fileSecurity);
} catch (PrivilegeNotHeldException e) {
// Non-critical, user might have no rights to manage the resource
ASF.ArchiLogger.LogGenericDebuggingException(e);
}
} else if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) {
fileInfo.UnixFileMode |= UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
}
} catch (IOException) {
// Ignored, if the file was already created in the meantime by another instance, this is fine
}
return false;
}
}

View File

@@ -29,18 +29,19 @@ using JetBrains.Annotations;
namespace ArchiSteamFarm.Helpers.Json;
[PublicAPI]
public sealed class BooleanNumberConverter : JsonConverter<bool> {
public sealed class BooleanNormalizationConverter : JsonConverter<bool> {
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch {
JsonTokenType.True => true,
JsonTokenType.False => false,
JsonTokenType.Number => reader.GetByte() == 1,
JsonTokenType.String => reader.GetString() == "1",
_ => throw new JsonException()
};
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) {
ArgumentNullException.ThrowIfNull(writer);
writer.WriteNumberValue(value ? 1 : 0);
writer.WriteBooleanValue(value);
}
}

View File

@@ -28,6 +28,7 @@ using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
@@ -38,29 +39,33 @@ namespace ArchiSteamFarm.Helpers.Json;
public static class JsonUtilities {
[PublicAPI]
public static readonly JsonSerializerOptions DefaultJsonSerialierOptions = CreateDefaultJsonSerializerOptions();
public static readonly JsonSerializerOptions DefaultJsonSerializerOptions = CreateDefaultJsonSerializerOptions();
[PublicAPI]
public static readonly JsonSerializerOptions IndentedJsonSerialierOptions = CreateDefaultJsonSerializerOptions(true);
public static readonly JsonSerializerOptions IndentedJsonSerializerOptions = CreateDefaultJsonSerializerOptions(true);
[PublicAPI]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
public static JsonElement ToJsonElement<T>(this T obj) where T : notnull {
ArgumentNullException.ThrowIfNull(obj);
return JsonSerializer.SerializeToElement(obj, DefaultJsonSerialierOptions);
return JsonSerializer.SerializeToElement(obj, DefaultJsonSerializerOptions);
}
[PublicAPI]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
public static T? ToJsonObject<T>(this JsonElement jsonElement) => jsonElement.Deserialize<T>(DefaultJsonSerialierOptions);
public static T? ToJsonObject<T>(this JsonElement jsonElement) => jsonElement.Deserialize<T>(DefaultJsonSerializerOptions);
[PublicAPI]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
public static T? ToJsonObject<T>(this JsonNode jsonNode) => jsonNode.Deserialize<T>(DefaultJsonSerializerOptions);
[PublicAPI]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
public static async ValueTask<T?> ToJsonObject<T>(this Stream stream, CancellationToken cancellationToken = default) {
ArgumentNullException.ThrowIfNull(stream);
return await JsonSerializer.DeserializeAsync<T>(stream, DefaultJsonSerialierOptions, cancellationToken).ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<T>(stream, DefaultJsonSerializerOptions, cancellationToken).ConfigureAwait(false);
}
[PublicAPI]
@@ -68,12 +73,12 @@ public static class JsonUtilities {
public static T? ToJsonObject<T>([StringSyntax(StringSyntaxAttribute.Json)] this string json) {
ArgumentException.ThrowIfNullOrEmpty(json);
return JsonSerializer.Deserialize<T>(json, DefaultJsonSerialierOptions);
return JsonSerializer.Deserialize<T>(json, DefaultJsonSerializerOptions);
}
[PublicAPI]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
public static string ToJsonText<T>(this T obj, bool writeIndented = false) => JsonSerializer.Serialize(obj, writeIndented ? IndentedJsonSerialierOptions : DefaultJsonSerialierOptions);
public static string ToJsonText<T>(this T obj, bool writeIndented = false) => JsonSerializer.Serialize(obj, writeIndented ? IndentedJsonSerializerOptions : DefaultJsonSerializerOptions);
private static void ApplyCustomModifiers(JsonTypeInfo jsonTypeInfo) {
ArgumentNullException.ThrowIfNull(jsonTypeInfo);

View File

@@ -67,7 +67,7 @@ public abstract class SerializableFile : IDisposable {
ArgumentNullException.ThrowIfNull(serializableFile);
if (string.IsNullOrEmpty(serializableFile.FilePath)) {
throw new InvalidOperationException(nameof(serializableFile.FilePath));
return;
}
if (serializableFile.ReadOnly) {
@@ -104,19 +104,11 @@ public abstract class SerializableFile : IDisposable {
// We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
string newFilePath = $"{serializableFile.FilePath}.new";
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(serializableFile.FilePath)) {
string currentJson = await File.ReadAllTextAsync(serializableFile.FilePath).ConfigureAwait(false);
if (json == currentJson) {
return;
}
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
File.Replace(newFilePath, serializableFile.FilePath, null);
} else {
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
File.Move(newFilePath, serializableFile.FilePath);
}
} catch (Exception e) {
@@ -154,21 +146,15 @@ public abstract class SerializableFile : IDisposable {
try {
// We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
#pragma warning disable CA3003 // Ignored due to caller's intent
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(filePath)) {
string currentJson = await File.ReadAllTextAsync(filePath).ConfigureAwait(false);
if (json == currentJson) {
return true;
}
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
File.Replace(newFilePath, filePath, null);
} else {
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
File.Move(newFilePath, filePath);
}
#pragma warning restore CA3003 // Ignored due to caller's intent
return true;
} catch (Exception e) {

View File

@@ -30,6 +30,7 @@ using System.Net;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers.Json;
@@ -54,6 +55,7 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using NLog.Web;
using Scalar.AspNetCore;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace ArchiSteamFarm.IPC;
@@ -63,6 +65,8 @@ internal static class ArchiKestrel {
internal static HistoryTarget? HistoryTarget { get; private set; }
private static readonly SemaphoreSlim StateSemaphore = new(1, 1);
private static WebApplication? WebApplication;
internal static void OnNewHistoryTarget(HistoryTarget? historyTarget = null) {
@@ -77,49 +81,50 @@ internal static class ArchiKestrel {
}
}
internal static async Task Start() {
if (WebApplication != null) {
return;
}
ASF.ArchiLogger.LogGenericInfo(Strings.IPCStarting);
// Init history logger for /Api/Log usage
Logging.InitHistoryLogger();
WebApplication webApplication = await CreateWebApplication().ConfigureAwait(false);
internal static async Task Restart() {
await StateSemaphore.WaitAsync().ConfigureAwait(false);
try {
// Start the server
await webApplication.StartAsync().ConfigureAwait(false);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
await webApplication.DisposeAsync().ConfigureAwait(false);
await StopInternally().ConfigureAwait(false);
await StartInternally().ConfigureAwait(false);
} finally {
StateSemaphore.Release();
}
}
internal static async Task Start() {
if (IsRunning) {
return;
}
WebApplication = webApplication;
await StateSemaphore.WaitAsync().ConfigureAwait(false);
ASF.ArchiLogger.LogGenericInfo(Strings.IPCReady);
try {
await StartInternally().ConfigureAwait(false);
} finally {
StateSemaphore.Release();
}
}
internal static async Task Stop() {
if (WebApplication == null) {
if (!IsRunning) {
return;
}
await WebApplication.StopAsync().ConfigureAwait(false);
await WebApplication.DisposeAsync().ConfigureAwait(false);
await StateSemaphore.WaitAsync().ConfigureAwait(false);
WebApplication = null;
try {
await StopInternally().ConfigureAwait(false);
} finally {
StateSemaphore.Release();
}
}
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "PathString is a primitive, it's unlikely to be trimmed to the best of our knowledge")]
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3000", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")]
private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] ConfigurationManager configuration, WebApplication app) {
private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] ConfigurationManager configuration, IWebHostEnvironment environment, WebApplication app) {
ArgumentNullException.ThrowIfNull(configuration);
ArgumentNullException.ThrowIfNull(environment);
ArgumentNullException.ThrowIfNull(app);
// The order of dependency injection is super important, doing things in wrong order will most likely break everything
@@ -154,8 +159,10 @@ internal static class ArchiKestrel {
// This must be called before default files, because we don't know the exact file name that will be used for index page
app.UseWhen(static context => !context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), static appBuilder => appBuilder.UseStatusCodePagesWithReExecute("/"));
// Add support for default root path redirection (GET / -> GET /index.html), must come before static files
app.UseDefaultFiles();
if (!string.IsNullOrEmpty(environment.WebRootPath)) {
// Add support for default root path redirection (GET / -> GET /index.html), must come before static files
app.UseDefaultFiles();
}
// Add support for additional default files provided by plugins
Dictionary<string, string> pluginPaths = new(StringComparer.Ordinal);
@@ -218,12 +225,14 @@ internal static class ArchiKestrel {
app.UseStaticFiles(options);
}
// Add support for static files (e.g. HTML, CSS and JS from IPC GUI)
app.UseStaticFiles(
new StaticFileOptions {
OnPrepareResponse = OnPrepareResponse
}
);
if (!string.IsNullOrEmpty(environment.WebRootPath)) {
// Add support for static files (e.g. HTML, CSS and JS from IPC GUI)
app.UseStaticFiles(
new StaticFileOptions {
OnPrepareResponse = OnPrepareResponse
}
);
}
// Use routing for our API controllers, this should be called once we're done with all the static files mess
app.UseRouting();
@@ -242,6 +251,11 @@ internal static class ArchiKestrel {
// Add support for websockets that we use e.g. in /Api/NLog
app.UseWebSockets();
// Add support for output caching
if (ASF.GlobalConfig?.OptimizationMode != GlobalConfig.EOptimizationMode.MinMemoryUsage) {
app.UseOutputCache();
}
// Add additional endpoints provided by plugins
foreach (IWebServiceProvider plugin in PluginsCore.ActivePlugins.OfType<IWebServiceProvider>()) {
try {
@@ -255,17 +269,19 @@ internal static class ArchiKestrel {
app.MapControllers();
// Add support for OpenAPI, responsible for automatic API documentation generation, this should be on the end, once we're done with API
app.MapOpenApi("/swagger/{documentName}/swagger.json");
IEndpointConventionBuilder openApi = app.MapOpenApi("/swagger/{documentName}/swagger.json");
if (ASF.GlobalConfig?.OptimizationMode != GlobalConfig.EOptimizationMode.MinMemoryUsage) {
openApi.CacheOutput();
}
// Add support for swagger UI, this should be after swagger, obviously
app.UseSwaggerUI(
static options => {
options.DisplayRequestDuration();
options.EnableDeepLinking();
options.EnableTryItOutByDefault();
options.ShowCommonExtensions();
options.ShowExtensions();
options.SwaggerEndpoint($"{SharedInfo.ASF}/swagger.json", $"{SharedInfo.ASF} API");
app.MapScalarApiReference(
"/swagger", static options => {
options.DefaultFonts = false;
options.OpenApiRoutePattern = $"/swagger/{SharedInfo.ASF}/swagger.json";
options.Theme = ScalarTheme.Kepler;
options.Title = $"{SharedInfo.AssemblyName} API";
}
);
}
@@ -302,8 +318,7 @@ internal static class ArchiKestrel {
}
// Add support for proxies
services.Configure<ForwardedHeadersOptions>(
options => {
services.Configure<ForwardedHeadersOptions>(options => {
options.ForwardedHeaders = ForwardedHeaders.All;
if (knownNetworks != null) {
@@ -331,6 +346,11 @@ internal static class ArchiKestrel {
services.AddCors(static options => options.AddDefaultPolicy(static policyBuilder => policyBuilder.AllowAnyOrigin()));
}
// Add support for output caching
if (ASF.GlobalConfig?.OptimizationMode != GlobalConfig.EOptimizationMode.MinMemoryUsage) {
services.AddOutputCache();
}
// Add support for OpenAPI, responsible for automatic API documentation generation
services.AddOpenApi(
SharedInfo.ASF, static options => {
@@ -340,7 +360,7 @@ internal static class ArchiKestrel {
}
);
// Add support for optional healtchecks
// Add support for optional health-checks
services.AddHealthChecks();
// Add support for additional services provided by plugins
@@ -352,9 +372,8 @@ internal static class ArchiKestrel {
}
}
services.ConfigureHttpJsonOptions(
static options => {
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerialierOptions : JsonUtilities.DefaultJsonSerialierOptions;
services.ConfigureHttpJsonOptions(static options => {
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerializerOptions : JsonUtilities.DefaultJsonSerializerOptions;
options.SerializerOptions.PropertyNamingPolicy = jsonSerializerOptions.PropertyNamingPolicy;
options.SerializerOptions.TypeInfoResolver = jsonSerializerOptions.TypeInfoResolver;
@@ -378,9 +397,8 @@ internal static class ArchiKestrel {
mvc.AddControllersAsServices();
// Modify default JSON options
mvc.AddJsonOptions(
static options => {
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerialierOptions : JsonUtilities.DefaultJsonSerialierOptions;
mvc.AddJsonOptions(static options => {
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerializerOptions : JsonUtilities.DefaultJsonSerializerOptions;
options.JsonSerializerOptions.PropertyNamingPolicy = jsonSerializerOptions.PropertyNamingPolicy;
options.JsonSerializerOptions.TypeInfoResolver = jsonSerializerOptions.TypeInfoResolver;
@@ -440,8 +458,7 @@ internal static class ArchiKestrel {
builder.WebHost.UseConfiguration(new ConfigurationBuilder().SetBasePath(absoluteConfigDirectory).AddJsonFile(SharedInfo.IPCConfigFile, false, true).Build());
}
builder.WebHost.ConfigureKestrel(
options => {
builder.WebHost.ConfigureKestrel(options => {
options.AddServerHeader = false;
if (customConfigExists) {
@@ -466,7 +483,7 @@ internal static class ArchiKestrel {
WebApplication result = builder.Build();
ConfigureApp(builder.Configuration, result);
ConfigureApp(builder.Configuration, builder.Environment, result);
return result;
}
@@ -505,4 +522,43 @@ internal static class ArchiKestrel {
headers.CacheControl = cacheControl;
}
private static async Task StartInternally() {
if (WebApplication != null) {
return;
}
ASF.ArchiLogger.LogGenericInfo(Strings.IPCStarting);
// Init history logger for /Api/Log usage
Logging.InitHistoryLogger();
WebApplication webApplication = await CreateWebApplication().ConfigureAwait(false);
try {
// Start the server
await webApplication.StartAsync().ConfigureAwait(false);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
await webApplication.DisposeAsync().ConfigureAwait(false);
return;
}
WebApplication = webApplication;
ASF.ArchiLogger.LogGenericInfo(Strings.IPCReady);
}
private static async Task StopInternally() {
if (WebApplication == null) {
return;
}
await WebApplication.StopAsync().ConfigureAwait(false);
await WebApplication.DisposeAsync().ConfigureAwait(false);
WebApplication = null;
}
}

View File

@@ -23,6 +23,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
@@ -33,6 +34,7 @@ using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Data;
using ArchiSteamFarm.Steam.Storage;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -151,6 +153,10 @@ public sealed class BotController : ArchiController {
request.BotConfig.SteamParentalCode = bot.BotConfig.SteamParentalCode;
}
if (!request.BotConfig.IsWebProxyPasswordSet && bot.BotConfig.IsWebProxyPasswordSet) {
request.BotConfig.WebProxyPassword = bot.BotConfig.WebProxyPassword;
}
if (bot.BotConfig.AdditionalProperties?.Count > 0) {
request.BotConfig.AdditionalProperties ??= new Dictionary<string, JsonElement>(bot.BotConfig.AdditionalProperties.Count, bot.BotConfig.AdditionalProperties.Comparer);
@@ -277,6 +283,81 @@ public sealed class BotController : ArchiController {
return Ok(results.All(static result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed));
}
[EndpointSummary("Fetches specific inventory of given bots")]
[HttpGet("{botNames:required}/Inventory/{appID}/{contextID}")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, BotInventoryResponse>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> InventoryGet(string botNames, uint appID, ulong contextID, [FromQuery] string? language = null) {
ArgumentException.ThrowIfNullOrEmpty(botNames);
if (appID == 0) {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsInvalid(nameof(appID))));
}
if (contextID == 0) {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsInvalid(nameof(contextID))));
}
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
}
IList<(HashSet<Asset>? Result, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.GetInventory(appID, contextID, language: language))).ConfigureAwait(false);
Dictionary<string, BotInventoryResponse> result = new(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) {
(HashSet<Asset>? inventory, _) = results[result.Count];
if (inventory == null) {
result[bot.BotName] = new BotInventoryResponse();
continue;
}
HashSet<CEcon_Asset> assets = new(inventory.Count);
HashSet<CEconItem_Description> descriptions = [];
foreach (Asset asset in inventory) {
assets.Add(asset.Body);
if (asset.Description != null) {
descriptions.Add(asset.Description.Body);
}
}
result[bot.BotName] = new BotInventoryResponse(assets, descriptions);
}
return Ok(new GenericResponse<IReadOnlyDictionary<string, BotInventoryResponse>>(result));
}
[EndpointSummary("Fetches general inventory information of given bots")]
[HttpGet("{botNames:required}/Inventory")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, ImmutableDictionary<uint, InventoryAppData>>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> InventoryInfoGet(string botNames) {
ArgumentException.ThrowIfNullOrEmpty(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
}
IList<ImmutableDictionary<uint, InventoryAppData>?> results = await Utilities.InParallel(bots.Select(static bot => bot.ArchiWebHandler.GetInventoryContextData())).ConfigureAwait(false);
Dictionary<string, ImmutableDictionary<uint, InventoryAppData>?> result = new(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) {
result[bot.BotName] = results[result.Count];
}
return Ok(new GenericResponse<IReadOnlyDictionary<string, ImmutableDictionary<uint, InventoryAppData>?>>(result));
}
[EndpointSummary("Pauses given bots")]
[HttpPost("{botNames:required}/Pause")]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.OK)]
@@ -358,6 +439,35 @@ public sealed class BotController : ArchiController {
return Ok(new GenericResponse<IReadOnlyDictionary<string, IReadOnlyDictionary<string, CStore_RegisterCDKey_Response?>>>(result.Values.SelectMany(static responses => responses.Values).All(static value => value != null), result));
}
[EndpointSummary("Removes licenses on given bots")]
[HttpPost("{botNames:required}/RemoveLicense")]
[ProducesResponseType<GenericResponse<IReadOnlyDictionary<string, BotRemoveLicenseResponse>>>((int) HttpStatusCode.OK)]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> RemoveLicensePost(string botNames, [FromBody] BotRemoveLicenseRequest request) {
ArgumentException.ThrowIfNullOrEmpty(botNames);
ArgumentNullException.ThrowIfNull(request);
if ((request.Apps?.IsEmpty != false) && (request.Packages?.IsEmpty != false)) {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsEmpty($"{nameof(request.Apps)} && {nameof(request.Packages)}")));
}
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, Strings.FormatBotNotFound(botNames)));
}
IList<BotRemoveLicenseResponse> results = await Utilities.InParallel(bots.Select(bot => RemoveLicense(bot, request))).ConfigureAwait(false);
Dictionary<string, BotRemoveLicenseResponse> result = new(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) {
result[bot.BotName] = results[result.Count];
}
return Ok(new GenericResponse<IReadOnlyDictionary<string, BotRemoveLicenseResponse>>(result));
}
[EndpointSummary("Renames given bot along with all its related files")]
[HttpPost("{botName:required}/Rename")]
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.OK)]
@@ -478,4 +588,42 @@ public sealed class BotController : ArchiController {
return new BotAddLicenseResponse(apps, packages);
}
private static async Task<BotRemoveLicenseResponse> RemoveLicense(Bot bot, BotRemoveLicenseRequest request) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(request);
Dictionary<uint, EResult>? apps = null;
Dictionary<uint, EResult>? packages = null;
if (request.Apps != null) {
apps = new Dictionary<uint, EResult>(request.Apps.Count);
foreach (uint appID in request.Apps) {
if (!bot.IsConnectedAndLoggedOn) {
apps[appID] = EResult.Timeout;
continue;
}
apps[appID] = await bot.Actions.RemoveLicenseApp(appID).ConfigureAwait(false);
}
}
if (request.Packages != null) {
packages = new Dictionary<uint, EResult>(request.Packages.Count);
foreach (uint subID in request.Packages) {
if (!bot.IsConnectedAndLoggedOn) {
packages[subID] = EResult.Timeout;
continue;
}
packages[subID] = await bot.Actions.RemoveLicensePackage(subID).ConfigureAwait(false);
}
}
return new BotRemoveLicenseResponse(apps, packages);
}
}

View File

@@ -70,10 +70,6 @@ public sealed class NLogController : ArchiController {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsInvalid(nameof(lastAt))));
}
if (!Logging.LogFileExists) {
return BadRequest(new GenericResponse(false, Strings.FormatErrorIsEmpty(nameof(SharedInfo.LogFile))));
}
string[]? lines = await Logging.ReadLogFileLines().ConfigureAwait(false);
if ((lines == null) || (lines.Length == 0)) {

View File

@@ -52,7 +52,7 @@ internal sealed class OperationTransformer : IOpenApiOperationTransformer {
}
},
Array.Empty<string>()
[]
}
}
);

View File

@@ -0,0 +1,43 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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 System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
namespace ArchiSteamFarm.IPC.Requests;
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class BotRemoveLicenseRequest {
[Description("A collection (set) of apps (appIDs) to remove license for")]
[JsonInclude]
public ImmutableList<uint>? Apps { get; private init; }
[Description("A collection (set) of packages (subIDs) to remove license for")]
[JsonInclude]
public ImmutableList<uint>? Packages { get; private init; }
[JsonConstructor]
private BotRemoveLicenseRequest() { }
}

View File

@@ -0,0 +1,45 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Text.Json.Serialization;
using SteamKit2.Internal;
namespace ArchiSteamFarm.IPC.Responses;
public sealed class BotInventoryResponse {
[Description("Inventory assets")]
[JsonInclude]
public ImmutableHashSet<CEcon_Asset>? Assets { get; private init; }
[Description("Descriptions of the inventory assets")]
[JsonInclude]
public ImmutableHashSet<CEconItem_Description>? Descriptions { get; private init; }
internal BotInventoryResponse(IEnumerable<CEcon_Asset>? assets = null, IEnumerable<CEconItem_Description>? descriptions = null) {
Assets = assets?.ToImmutableHashSet();
Descriptions = descriptions?.ToImmutableHashSet();
}
}

View File

@@ -0,0 +1,45 @@
// ----------------------------------------------------------------------------------------------
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// ----------------------------------------------------------------------------------------------
// |
// Copyright 2015-2025 Ł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.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Text.Json.Serialization;
using SteamKit2;
namespace ArchiSteamFarm.IPC.Responses;
public sealed class BotRemoveLicenseResponse {
[Description("A collection (set) of apps (appIDs) to ask license for")]
[JsonInclude]
public ImmutableDictionary<uint, EResult>? Apps { get; private init; }
[Description("A collection (set) of packages (subIDs) to ask license for")]
[JsonInclude]
public ImmutableDictionary<uint, EResult>? Packages { get; private init; }
internal BotRemoveLicenseResponse(IReadOnlyDictionary<uint, EResult>? apps, IReadOnlyDictionary<uint, EResult>? packages) {
Apps = apps?.ToImmutableDictionary();
Packages = packages?.ToImmutableDictionary();
}
}

View File

@@ -501,6 +501,7 @@ StackTrace:
<data name="PluginsWarning" xml:space="preserve">
@@ -571,5 +572,6 @@ StackTrace:
</root>

View File

@@ -578,6 +578,7 @@
<value>Ботът има ниво {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Сравняват се Steam предмети, #{0} път...</value>
<comment>{0} will be replaced by round number</comment>
@@ -733,4 +734,5 @@
</root>

View File

@@ -380,6 +380,7 @@
</data>
<data name="ErrorAborted" xml:space="preserve">
<value>Obustavljeno!</value>
</data>
@@ -450,6 +451,7 @@
</root>

View File

@@ -338,6 +338,7 @@
<data name="Executing" xml:space="preserve">
@@ -391,6 +392,7 @@
</root>

View File

@@ -584,6 +584,10 @@ StackTrace:
<value>Bot má level {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} aktiva</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Srovnávám položky ze Steamu, kolo #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -792,4 +796,8 @@ StackTrace:
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Vlastní zásuvné moduly byly zaregistrovány pro automatické aktualizace. Tým ASF by vám rád připomněl, že pro vaši vlastní bezpečnost byste měli povolit automatické aktualizace pouze od důvěryhodných stran. Pokud jste to nechtěli udělat, můžete aktualizace zásuvných modulů zakázat v globální konfiguraci ASF.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Input: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -584,6 +584,10 @@ Processens oppetid: {1}</value>
<value>Bot har niveau {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} aktiver</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Matcher Steam inventar, runde #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -683,21 +687,35 @@ Processens oppetid: {1}</value>
<value>Din krypteringsnøgle er for kort. Vi anbefaler at bruge en, der er mindst {0} bytes (tegn) lang.</value>
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
</data>
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
<value>Du bruger {0} indstilling af {1} ejendom, men du har ikke angivet en brugerdefineret --cryptkey. Du kan eventuelt give en brugerdefineret --cryptkey for øget sikkerhed, hvis du gerne vil.</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 bruger {0} indstilling af {1} ejendom, men du har ikke angivet en brugerdefineret --cryptkey. Dette går helt ud over beskyttelsen, da ASF er tvunget til at bruge sin egen (kendte) nøgle. Du bør give en brugerdefineret --cryptkey til at gøre brug af den sikkerhedsmæssige fordel, som denne indstilling.</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>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="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
<value>Du kører ASF i ikke-understøttet miljø, leverer --ignore-unsupported-environment argument. Bemærk, at vi ikke tilbyder nogen form for støtte til dette scenarie, og du gør det helt på egen risiko. Du er blevet advaret.</value>
</data>
<data name="FetchingChecksumFromRemoteServer" xml:space="preserve">
<value>Henter checksum fra den eksterne server...</value>
</data>
<data name="VerifyingChecksumWithRemoteServer" xml:space="preserve">
<value>Verificerer checksum af den downloadede binary mod den fra den eksterne server...</value>
</data>
<data name="ChecksumMissing" xml:space="preserve">
<value>Fjernserveren ved ikke noget om den udgivelse, vi opdaterer til. Denne situation er mulig, hvis udgivelsen blev offentliggjort for nylig - nægter at gå videre med opdateringsproceduren med det samme som en yderligere sikkerhedsforanstaltning.</value>
</data>
<data name="ChecksumTimeout" xml:space="preserve">
<value>Kunne ikke hente checksum af den downloadede binære - nægter at fortsætte med opdateringsproceduren på dette tidspunkt som en ekstra sikkerhedsforanstaltning.</value>
</data>
<data name="ChecksumWrong" xml:space="preserve">
<value>Fjernserver har svaret med en anden tjeksum, dette kan indikere beskadiget download eller MITM angreb, nægter at fortsætte med opdateringsproceduren!</value>
</data>
<data name="PatchingFiles" xml:space="preserve">
<value>Retter ASF-filer...</value>
</data>
@@ -717,8 +735,13 @@ Processens oppetid: {1}</value>
<value>ASF kan ikke bruge app {0}, da den har regionsrelateret begrænsning for {1} der varer indtil {2}.</value>
<comment>{0} will be replaced by app ID (number), {1} will be replaced by short country code (string, such as "PL"), {2} will be replaced by human-readable date (string).</comment>
</data>
<data name="WarningUnsupportedOfficialPlugins" xml:space="preserve">
<value>Du forsøger at køre officielle {0} plugin i forkert matchet med ASF version: {1} (forventet {2}). Dette tyder på, at du gør noget forfærdeligt forkert, enten lave din opsætning eller forsyning - ignorer-ikke-understøttet miljø-argument, hvis du virkelig ved, hvad du laver.</value>
<comment>{0} will be replaced by plugin name, {1} will be replaced by plugin's version number, {2} will be replaced by ASF's version number.</comment>
</data>
<data name="ErrorTooManyCrashes" xml:space="preserve">
<value>Din ASF er styrtet for mange gange for nylig, og på grund af at processen initialisering er blevet deaktiveret. Enten undersøge, rette din opsætning, og fjern derefter ASF. udslæt fil fra din config mappe, eller forsyning -- ignore-unsupported-environment argument hvis du virkelig ved, hvad du laver.</value>
</data>
<data name="IdlingGameNotPossiblePrivate" xml:space="preserve">
<value>Idling {0} ({1}) er deaktiveret, da spillet i øjeblikket er markeret som privat. Hvis du har til hensigt fra ASF til at idle det spil, så overvej at ændre dine privatlivsindstillinger.</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
@@ -746,8 +769,14 @@ Processens oppetid: {1}</value>
<value>Fandt {0} plugin opdatering fra version {1} til {2}...</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateNoAssetFound" xml:space="preserve">
<value>Ingen aktiv tilgængelig for {0} plugin opdatering fra version {1} til {2}, dette betyder normalt, at opdateringen vil være tilgængelig på senere tidspunkt.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateConflictingAssetsFound" xml:space="preserve">
<value>Intet aktiv kunne bestemmes for {0} plugin opdatering fra version {1} til {2}. Dette kan ske, hvis udgivelsen ikke er færdig endnu - hvis det bliver ved med at ske, bør du underrette plugin skaberen om det.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateInProgress" xml:space="preserve">
<value>Opdaterer {0} plugin...</value>
<comment>{0} will be replaced by plugin name (string).</comment>
@@ -764,5 +793,11 @@ Processens oppetid: {1}</value>
<value>{0} ({1}) plugin er blevet deaktiveret fra automatiske opdateringer, på trods af at den understøtter den funktion.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
</data>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Tilpassede plugins er blevet registreret til automatiske opdateringer. ASF team vil gerne minde dig om, at for din egen sikkerhed, bør du kun aktivere automatiske opdateringer fra betroede parter. Hvis du ikke har til hensigt at gøre dette, kan du deaktivere plugin opdateringer i ASF global konfiguration.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Input: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -585,6 +585,10 @@ Prozesslaufzeit: {1}</value>
<value>Der Bot hat das Level {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} Gegenstände</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Vergleiche Steam-Gegenstände, Durchgang #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -759,7 +763,7 @@ Prozesslaufzeit: {1}</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateNewVersionAvailable" xml:space="preserve">
<value>Es ist eine neue ASF-Version verfügbar! Erwägen Sie, das Programm selbst zu aktualisieren!</value>
<value>Es ist eine neue Plugin-Version verfügbar! Erwägen Sie, dieses selbst zu aktualisieren!</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateFound" xml:space="preserve">
@@ -793,4 +797,8 @@ Prozesslaufzeit: {1}</value>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Benutzerdefinierte Erweiterungen wurden für automatische Updates registriert. Das ASF-Team möchte Sie daran erinnern, dass Sie zu Ihrer eigenen Sicherheit automatische Updates nur von vertrauenswürdigen Parteien aktivieren sollten. Wenn Sie dies nicht beabsichtigen, können Sie Plugin-Updates in der globalen ASF-Konfuguration deaktivieren.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Eingabe: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -584,6 +584,10 @@ StackTrace:
<value>Το bot βρίσκεται στο επίπεδο {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} assets</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Ταίριασμα στοιχείων Steam, γύρος #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -706,7 +710,9 @@ StackTrace:
<data name="ChecksumMissing" xml:space="preserve">
<value>Ο απομακρυσμένος διακομιστής δε γνωρίζει τίποτα σχετικά με την έκδοση στην οποία ενημερώνουμε. Αυτή η κατάσταση είναι πιθανή εάν η νέα έκδοση δημοσιεύτηκε πρόσφατα - Ως πρόσθετο μέτρο ασφαλείας το πρόγραμμα δε θα προβεί στην άμεση διαδικασία ενημέρωσης.</value>
</data>
<data name="ChecksumTimeout" xml:space="preserve">
<value>Αποτυχία λήψης του αθροίσματος ελέγχου του ληφθέντος δυαδικού - άρνηση να προχωρήσει με τη διαδικασία ενημέρωσης αυτή τη στιγμή ως πρόσθετο μέτρο ασφαλείας.</value>
</data>
<data name="ChecksumWrong" xml:space="preserve">
<value>Ο απομακρυσμένος διακομιστής απάντησε με ένα διαφορετικό checksum, αυτό μπορεί να υποδεικνύει κατεστραμμένο download ή MITM attack, αρνούμενος να προχωρήσει με τη διαδικασία ενημέρωσης!</value>
</data>
@@ -729,9 +735,17 @@ StackTrace:
<value>Το ASF δεν είναι σε θέση να εκτελέσει την εφαρμογή {0} λόγω γεωγραφικού περιορισμού ως προς τη χώρα {1} που διαρκεί έως {2}.</value>
<comment>{0} will be replaced by app ID (number), {1} will be replaced by short country code (string, such as "PL"), {2} will be replaced by human-readable date (string).</comment>
</data>
<data name="WarningUnsupportedOfficialPlugins" xml:space="preserve">
<value>Προσπαθείτε να εκτελέσετε επίσημο πρόσθετο {0} σε αναντιστοιχία με την έκδοση ASF: {1} (αναμένεται {2}). Αυτό υποδηλώνει ότι κάνετε κάτι τρομερά λάθος, είτε διορθώσετε τις ρυθμίσεις σας είτε παρέχετε την επιλογή --ignore-unsupported-environment αν πραγματικά ξέρετε τι κάνετε.</value>
<comment>{0} will be replaced by plugin name, {1} will be replaced by plugin's version number, {2} will be replaced by ASF's version number.</comment>
</data>
<data name="ErrorTooManyCrashes" xml:space="preserve">
<value>Το ASF σας συνετρίβη πάρα πολλές φορές πρόσφατα και λόγω αυτού η έναρξη της διαδικασίας έχει απενεργοποιηθεί. Είτε διερευνήσετε, να διορθώσετε την εγκατάστασή σας και, στη συνέχεια, αφαιρέστε το ASF. rash file from your config directory, or supply --ignore-unsupported-environment argument if you really know what you're doing .</value>
</data>
<data name="IdlingGameNotPossiblePrivate" 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="WarningSkipping" xml:space="preserve">
<value>Παράλειψη: {0}...</value>
<comment>{0} will be replaced by text value (string) of entry being skipped.</comment>
@@ -747,13 +761,37 @@ StackTrace:
<value>Δεν υπάρχει διαθέσιμη ενημέρωση για το πρόσθετο {0}: {1} ≥ {2}.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateNewVersionAvailable" xml:space="preserve">
<value>Νέα έκδοση {0} plugin είναι διαθέσιμη! Σκεφτείτε να ενημερώσετε τον εαυτό σας!</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateFound" xml:space="preserve">
<value>Βρέθηκε ενημέρωση του πρόσθετου {0} από την έκδοση {1} σε {2}...</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateInProgress" xml:space="preserve">
<value>Ενημέρωση του πρόσθετου {0}...</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateFinished" xml:space="preserve">
<value>Επιτυχής ενημέρωση του {0} plugin, οι αλλαγές θα φορτωθούν στην επόμενη εκκίνηση του ASF.</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateEnabled" xml:space="preserve">
<value>{0}/{1} plugin έχει καταχωρηθεί και ενεργοποιηθεί για αυτόματες ενημερώσεις.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
</data>
<data name="PluginUpdateDisabled" xml:space="preserve">
<value>{0} ({1}) plugin έχει απενεργοποιηθεί από αυτόματες ενημερώσεις, παρά την υποστήριξη αυτής της δυνατότητας.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
</data>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Τα προσαρμοσμένα πρόσθετα έχουν καταχωρηθεί για αυτόματες ενημερώσεις. Ομάδα ASF θα ήθελε να σας υπενθυμίσει ότι, για τη δική σας ασφάλεια, θα πρέπει να ενεργοποιήσετε την αυτόματη ενημέρωση μόνο από αξιόπιστα μέρη. Αν δεν προτίθεστε να το κάνετε αυτό, μπορείτε να απενεργοποιήσετε ενημερώσεις plugin σε γενικές ρυθμίσεις ASF.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Input: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -583,6 +583,10 @@ Tiempo de actividad del proceso: {1}</value>
<value>El bot tiene nivel {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} elementos</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Emparejando artículos de Steam, ronda #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -791,4 +795,8 @@ Tiempo de actividad del proceso: {1}</value>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Los plugins personalizados han sido registados para actualizaciones automáticas. El equipo de ASF desea recordarte que, por tu propia seguridad, únicamente deberías habilitar las actualizaciones automáticas de fuentes confiables. Si no pretendías hacer esto, puedes deshabilitar la actualización de plugins en la configuración global de ASF.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Entrada: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -541,6 +541,7 @@ StackTrace:
</data>
<data name="ErrorAborted" xml:space="preserve">
<value>کنسل شد!</value>
</data>
@@ -620,4 +621,5 @@ StackTrace:
</root>

View File

@@ -87,7 +87,10 @@ StackTrace:
{2}</value>
<comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment>
</data>
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
<value>Poistutaan virhekoodilla {0}!</value>
<comment>{0} will be replaced by error code (number)</comment>
</data>
<data name="ErrorFailingRequest" xml:space="preserve">
<value>Pyyntö epäonnistuu: {0}</value>
<comment>{0} will be replaced by URL of the request</comment>
@@ -581,6 +584,10 @@ Prosessin käyttöaika: {1}</value>
<value>Botin taso on {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} assettia</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Vertaillaan Steam-esineitä, kierros #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -680,7 +687,10 @@ Prosessin käyttöaika: {1}</value>
<value>Salausavaimesi on liian lyhyt. Suosittelemme käyttämään salausavainta, joka on vähintään {0} tavua (merkkiä) pitkä.</value>
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
</data>
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
<value>Käytät ominaisuuden {1} asetusta {0}, mutta et antanut mukautettua --cryptkey -argumenttia. Sinun tulisi antaa mukautettu --cryptkey lisäturvallisuuden takaamiseksi, jos haluat.</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>Käytät ominaisuuden {1} asetusta {0}, mutta et antanut mukautettua --cryptkey -argumenttia. Tämä tekee suojauksen täysin tyhjäksi, koska ASF joutuu käyttämään omaa (tunnettua) avaintaan. Sinun tulisi antaa mukautettu --cryptkey tämän asetuksen turvallisuushyöydyn saavuttamiseksi.</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>
@@ -700,7 +710,9 @@ Prosessin käyttöaika: {1}</value>
<data name="ChecksumMissing" xml:space="preserve">
<value>Etäpalvelin ei tiedä mitään julkaisusta, johon olemme päivittämässä. Tämä tilanne on mahdollinen, jos julkistaminen on hiljattain julkaistu - kieltäydytään jatkamasta päivitystä ylimääräisenä turvatoimenpiteenä.</value>
</data>
<data name="ChecksumTimeout" xml:space="preserve">
<value>Virhe noudettaessa tarkistussummaa ladatulle binäärille - kieltäydytään jatkamasta päivitysmenettelyä tällä hetkellä lisäsuojaustoimenpiteenä.</value>
</data>
<data name="ChecksumWrong" xml:space="preserve">
<value>Etäpalvelin on vastannut eri tarkistussummalla, mikä saattaa merkitä viallista latausta tai MITM-hyökkäystä, kieltäydytään jatkamasta päivitystä!</value>
</data>
@@ -723,20 +735,69 @@ Prosessin käyttöaika: {1}</value>
<value>ASF ei pysty pelaamaan sovellusta {0}, koska sillä on aluekohtainen rajoitus maassa {1}, joka kestää {2} asti.</value>
<comment>{0} will be replaced by app ID (number), {1} will be replaced by short country code (string, such as "PL"), {2} will be replaced by human-readable date (string).</comment>
</data>
<data name="WarningUnsupportedOfficialPlugins" xml:space="preserve">
<value>Yrität suorittaa virallista {0} -liitännäistä, joka ei täsmää ASF-versioon: {1} (odotettiin {2}). Tämä viittaa siihen, että teet jotain hirvittävän väärin; joko korjaa asennuksesi tai anna --ignore-unsupported-environment argumentti, jos todella tiedät mitä teet.</value>
<comment>{0} will be replaced by plugin name, {1} will be replaced by plugin's version number, {2} will be replaced by ASF's version number.</comment>
</data>
<data name="ErrorTooManyCrashes" xml:space="preserve">
<value>ASF on kaatunut liian monta kertaa viime aikoina ja sen vuoksi prosessin alustus on poistettu käytöstä. Joko tutki, korjaa asetuksesi ja poista sitten ASF.crash -tiedosto asetushakemistostasi, tai anna --ignore-unsupported-environment -argumentti, jos todella tiedät mitä olet tekemässä.</value>
</data>
<data name="IdlingGameNotPossiblePrivate" xml:space="preserve">
<value>Pelin {0} ({1}) farmaus on poistettu käytöstä, koska kyseinen peli on tällä hetkellä merkitty yksityiseksi. Jos aiot ASF:n farmata tuota peliä, harkitse sen yksityisyysasetusten muuttamista.</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="WarningSkipping" xml:space="preserve">
<value>Ohitetaan: {0}...</value>
<comment>{0} will be replaced by text value (string) of entry being skipped.</comment>
</data>
<data name="PluginUpdatesChecking" xml:space="preserve">
<value>Tarkistetaan lisäosien päivityksiä...</value>
</data>
<data name="PluginUpdateChecking" xml:space="preserve">
<value>Tarkistetaan päivitystä lisäosalle {0}...</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateNotFound" xml:space="preserve">
<value>Ei päivitystä saatavilla lisäosalle {0}: {1} ≥ {2}.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateNewVersionAvailable" xml:space="preserve">
<value>Uusi versio {0} -laajennukesta on saatavilla! Suositellaan päivittämistä!</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateFound" xml:space="preserve">
<value>Löydetty laajennuksen {0} päivitys versiosta {1} versioon {2}...</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateNoAssetFound" xml:space="preserve">
<value>Lisäosan {0} päivitystä ei ole saatavilla versiosta {1} versioon {2}, tämä yleensä tarkoittaa, että päivitys on saatavilla myöhemmin.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateConflictingAssetsFound" xml:space="preserve">
<value>Assettia ei voitu selvittää laajennuksen {0} päivitykselle versiosta {1} versioon {2}. Tämä voi tapahtua, jos julkaisu ei ole vielä valmis - jos tämä jatkaa tapahtumasta, sinun tulisi ilmoittaa laajennuksen tekijälle asiasta.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
</data>
<data name="PluginUpdateInProgress" xml:space="preserve">
<value>Päivitetään lisäosa {0}...</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateFinished" xml:space="preserve">
<value>{0} -laajennuksen päivittäminen on onnistunut, muutokset ladataan seuraavan ASF-käynnistyksen yhteydessä.</value>
<comment>{0} will be replaced by plugin name (string).</comment>
</data>
<data name="PluginUpdateEnabled" xml:space="preserve">
<value>{0}/{1} -laajennus on rekisteröity ja automaattiset päivitykset otettu käyttöön.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
</data>
<data name="PluginUpdateDisabled" xml:space="preserve">
<value>{0} ({1}) -laajennukselta on poistettu automaattiset päivitykset huolimatta siitä että sillä on tällainen ominaisuus.</value>
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
</data>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Mukautettuja laajennuksia on rekisteröity automaattisia päivityksiä varten. ASF-tiimi haluaa muistuttaa sinua siitä, että oman turvallisuutesi vuoksi sinun pitäisi ottaa käyttöön automaattiset päivitykset vain luotettavilta osapuolilta. Jos et aikonut tehdä tätä, voit poistaa laajennukset päivitykset käytöstä ASF globaalissa asetuksessa.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Syöte: {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -584,6 +584,10 @@ Durée de fonctionnement : {1}</value>
<value>Le bot a le niveau {0}</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="BotInventory" xml:space="preserve">
<value>{0}/{1} ({2}/{3}): {4} articles</value>
<comment>{0} will be replaced by appID (number), {1} will be replaced by contextID (number), {2} will be replaced by app's name (string), {3} will be replaced by name of the context (string), {4} will be replaced by number of assets in the specified inventory (number).</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Correspondance des éléments Steam, tour #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -792,4 +796,8 @@ Durée de fonctionnement : {1}</value>
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Les plugins personnalisés ont été enregistrés pour les mises à jour automatiques. L'équipe ASF voudrait vous rappeler que, pour votre propre sécurité, vous ne devriez activer les mises à jour automatiques que des groupes de confiance. Si vous n'aviez pas l'intention de le faire, vous pouvez désactiver les mises à jour de plugin dans la configuration globale d'ASF.</value>
</data>
<data name="Input" xml:space="preserve">
<value>Saisie : {0}</value>
<comment>{0} will be replaced by text input from the user.</comment>
</data>
</root>

View File

@@ -581,6 +581,7 @@ StackTrace:
<value>לבוט יש רמה {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>מתאים פריטי Steam, שלב #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -736,4 +737,5 @@ StackTrace:
</root>

View File

@@ -352,6 +352,8 @@ Automatsko ažuriranje na tu verziju nije moguće.</value>

View File

@@ -582,6 +582,7 @@ Ennyi ideje fut: {1}</value>
<value>A bot szintje: {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Steam tárgyak egyeztetése #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -748,4 +749,5 @@ Ennyi ideje fut: {1}</value>
</root>

View File

@@ -580,6 +580,7 @@
<value>Bot mempunyai level {0}.</value>
<comment>{0} will be replaced by bot's level</comment>
</data>
<data name="ActivelyMatchingItems" xml:space="preserve">
<value>Mencocokkan item dari Steam, fase #{0}...</value>
<comment>{0} will be replaced by round number</comment>
@@ -788,4 +789,5 @@
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
<value>Plugin kustom telah didaftarkan untuk pembaruan otomatis. Tim ASF ingin mengingatkan Anda bahwa, demi keamanan Anda, Anda hanya boleh mengaktifkan pembaruan otomatis dari pihak yang tepercaya. Jika Anda tidak bermaksud melakukan ini, Anda dapat menonaktifkan pembaruan plugin di konfigurasi global ASF.</value>
</data>
</root>

Some files were not shown because too many files have changed in this diff Show More