Compare commits

..

726 Commits

Author SHA1 Message Date
ArchiBot
e0a8f96ec4 Automatic translations update 2022-01-23 02:07:51 +00:00
Archi
dae6f9d328 Use newer syntax for Enum.IsDefined() 2022-01-23 01:37:43 +01:00
Łukasz Domeradzki
4258fed873 Closes #2500 (#2501)
* Start work on #2500

* Update Bot.cs

* Misc refactor

* Update Bot.cs

* Add fallback for older plugins

* Misc

* Apply feedback
2022-01-23 00:14:14 +01:00
Renovate Bot
ab6e0a1e1b Update ASF-ui commit hash to 156992e 2022-01-22 18:51:14 +00:00
ArchiBot
959056523a Automatic translations update 2022-01-22 02:07:28 +00:00
Renovate Bot
245e3aa250 Update ASF-ui commit hash to 12ad1a4 2022-01-21 21:44:31 +00:00
Renovate Bot
170bd9fe42 Update ASF-ui commit hash to 1792331 2022-01-21 12:03:36 +00:00
Renovate Bot
2cf84d3691 Update ASF-ui commit hash to 351d4b7 2022-01-21 02:49:11 +00:00
ArchiBot
ae0ec5feee Automatic translations update 2022-01-21 02:09:07 +00:00
Renovate Bot
c495ad4f4a Update ASF-ui commit hash to 661a128 2022-01-20 20:44:19 +00:00
Renovate Bot
01e4085a52 Update ASF-ui commit hash to 2b2da73 2022-01-20 17:09:15 +00:00
ArchiBot
e89dad5792 Automatic translations update 2022-01-20 02:17:19 +00:00
Renovate Bot
8c6c7a5f3c Update ASF-ui commit hash to c985273 2022-01-19 21:20:37 +00:00
Renovate Bot
32f52e9de3 Update ASF-ui commit hash to 04a8efc 2022-01-19 03:26:14 +00:00
ArchiBot
aaabd81778 Automatic translations update 2022-01-19 02:07:42 +00:00
Renovate Bot
24200e3490 Update docker/build-push-action action to v2.8.0 2022-01-18 14:35:14 +00:00
Renovate Bot
a896075e88 Update ASF-ui commit hash to a3bc67f 2022-01-18 13:02:03 +00:00
ArchiBot
1bf35d1215 Automatic translations update 2022-01-18 02:15:40 +00:00
Renovate Bot
641aa435be Update wiki commit hash to ebfbf57 2022-01-17 20:37:03 +00:00
Renovate Bot
d3e48e69d4 Update ASF-ui commit hash to 1e5ccf7 2022-01-17 17:37:35 +00:00
Renovate Bot
8548044038 Update ASF-ui commit hash to 2c5aff8 2022-01-16 10:32:09 +00:00
ArchiBot
cdffde2d76 Automatic translations update 2022-01-16 02:16:57 +00:00
ArchiBot
afd7360676 Automatic translations update 2022-01-15 02:13:55 +00:00
Renovate Bot
7603efb289 Update ASF-ui commit hash to 94df465 2022-01-14 14:36:40 +00:00
Renovate Bot
3ad6f68bb9 Update crowdin/github-action action to v1.4.6 2022-01-14 10:25:23 +00:00
Renovate Bot
065facb5db Update ASF-ui commit hash to 6b2c2b6 2022-01-13 22:49:30 +00:00
Renovate Bot
8140784903 Update ASF-ui commit hash to 808b71f 2022-01-13 10:38:27 +00:00
Renovate Bot
c468f3e4e1 Update ASF-ui commit hash to 914506b 2022-01-13 01:34:43 +00:00
Renovate Bot
174317c674 Update ASF-ui commit hash to e48498f 2022-01-12 11:42:14 +00:00
ArchiBot
25690056da Automatic translations update 2022-01-12 09:33:03 +00:00
Renovate Bot
1950c1326e Update ASF-ui commit hash to 9301a40 2022-01-11 21:25:28 +00:00
Archi
876074a0ed Misc l10n 2022-01-11 12:27:05 +01:00
Renovate Bot
8c06051f52 Update ASF-ui commit hash to 6e8c8fd 2022-01-11 01:55:10 +00:00
Renovate Bot
b7d9c7b6da Update crowdin/github-action action to v1.4.5 2022-01-10 12:47:49 +00:00
Archi
ca048912cd Show ASF version in swagger spec
Also correct name to be more explicit
2022-01-10 12:49:05 +01:00
Archi
290aa3ba34 Bump 2022-01-10 11:21:50 +01:00
Archi
8620a90787 Remove all workarounds that should be no longer needed 2022-01-10 11:19:35 +01:00
Renovate Bot
189f998faf Update dependency SteamKit2 to v2.4.1 2022-01-10 03:06:39 +00:00
Archi
a5640f5a84 Fix permanently stopped IPC when ASF update has failed 2022-01-08 17:26:16 +01:00
Renovate Bot
b343d81f56 Update ASF-ui commit hash to 2089f03 2022-01-08 03:19:25 +00:00
Archi
edf2a19946 Add additional safeguards against running wrong package
e.g. Linux user calling dotnet ArchiSteamFarm.dll from win-x64 package
2022-01-07 19:08:40 +01:00
Archi
7e43a05517 Misc 2022-01-07 19:04:04 +01:00
Renovate Bot
db8ead92a1 Update ASF-ui commit hash to 44223fd 2022-01-07 05:34:52 +00:00
ArchiBot
e33c340183 Automatic translations update 2022-01-07 02:17:56 +00:00
Archi
a04781747e Bump 2022-01-06 20:48:26 +01:00
Archi
73bae63af6 Bullet-proofing 2022-01-06 20:44:17 +01:00
Archi
bf4bb7225c More Rider cleanups 2022-01-06 20:37:00 +01:00
Archi
1809028c77 Rider cleanup 2022-01-06 20:22:38 +01:00
Archi
c4b3899ae3 Bump 2022-01-06 20:18:56 +01:00
Archi
7c00e725d1 Closes #2483
I spent far too much time and sweat on this, so I'll just link this as explanation: https://github.com/SteamRE/SteamKit/pull/1075

HUGE THANKS to @xPaw for all the help, Pavel is the best
2022-01-06 20:01:03 +01:00
Renovate Bot
73dcb34c0c Update ASF-ui commit hash to 8b16b79 2022-01-06 05:47:57 +00:00
ArchiBot
65049bc2e5 Automatic translations update 2022-01-05 02:15:31 +00:00
Archi
b3ed87c9ef Add comment about built-in crypto miner
Got ya again
2022-01-04 21:25:30 +01:00
Renovate Bot
2ea5f5a83b Update ASF-ui commit hash to ca38e4f 2022-01-03 21:23:49 +00:00
Renovate Bot
ba1f832f54 Update ASF-ui commit hash to c25bd54 2022-01-03 04:34:01 +00:00
ArchiBot
39e7a73cd2 Automatic translations update 2022-01-03 02:13:56 +00:00
ArchiBot
d803887ef9 Automatic translations update 2022-01-02 02:15:55 +00:00
Renovate Bot
560d2400c0 Update ASF-ui commit hash to 61c51f7 2022-01-01 03:41:18 +00:00
ArchiBot
6a0cc973f3 Automatic translations update 2022-01-01 02:15:16 +00:00
Archi
b21742d06e Optimize selected GET calls that do not require session check preemptively
I've verified those to return login page and/or lostauth, we can save on excessive HEADs

It seems that all Steam GETs that return HTML are working like that, interesting
2021-12-31 16:55:29 +01:00
Archi
b76454ecfa Misc 2021-12-31 15:46:51 +01:00
Renovate Bot
376899ebe2 Update ASF-ui commit hash to 132d256 2021-12-31 10:13:56 +00:00
Renovate Bot
547bb13894 Update ASF-ui commit hash to 47d5a13 2021-12-31 03:09:34 +00:00
ArchiBot
c7792c8a1c Automatic translations update 2021-12-31 02:14:13 +00:00
ArchiBot
b67f92cc21 Automatic translations update 2021-12-30 02:12:38 +00:00
Renovate Bot
7ad05e1703 Update ASF-ui commit hash to 7006d2f 2021-12-29 23:32:37 +00:00
Archi
1ba2880071 Good catch 2021-12-28 23:50:02 +01:00
Archi
fd05a2cab6 Misc
I can imagine a very narrow edge case when waiting task would return just as the previous task releases the semaphore. This delay will prevent this from happening.
2021-12-28 23:44:12 +01:00
Archi
cd22d365ea Optimize HandleCallbacks() routine
We still need the semaphore to ensure we don't launch more than 1 task concurrently, but in unlikely case if we did, it'll just return on the initial call before the second one will finish, as we set KeepRunning = true before spawning a thread.

I don't see a reason why we'd need to enter semaphore on each loop, maybe I forgot about something, but it looks like Archi from the past just didn't notice that.
2021-12-28 18:16:34 +01:00
Renovate Bot
6196fc175e Update actions/setup-node action to v2.5.1 2021-12-28 15:13:10 +00:00
Renovate Bot
cd5835bdcb Update ASF-ui commit hash to 5dcfd68 2021-12-28 13:59:06 +00:00
Archi
07a7358493 Bump 2021-12-28 14:09:57 +01:00
Archi
475b8aa649 I lied 2021-12-28 14:09:38 +01:00
Archi
141c8835d0 Add error handling to inventory response on 5xx 2021-12-28 13:55:18 +01:00
Archi
6b498af3c9 Bump 2021-12-28 12:13:13 +01:00
Archi
640a794a3e Misc 2021-12-28 11:30:37 +01:00
ArchiBot
82cea76901 Automatic translations update 2021-12-28 02:12:56 +00:00
Archi
ffccb98d79 Fix NRE in WebLimitRequest()
This was possible if plugin triggered WebLimitRequest() for unrecognized service.
2021-12-27 16:03:33 +01:00
Renovate Bot
31bf21973b Update ASF-ui commit hash to e292b5e 2021-12-26 03:23:38 +00:00
ArchiBot
7dbb8e23b0 Automatic translations update 2021-12-26 02:15:27 +00:00
Renovate Bot
43d1ccfb0e Update ASF-ui commit hash to 40e8b05 2021-12-25 03:14:11 +00:00
ArchiBot
e2ff80cc46 Automatic translations update 2021-12-25 02:12:16 +00:00
ArchiBot
457bacfef8 Automatic translations update 2021-12-24 02:12:30 +00:00
Renovate Bot
44f9f12263 Update wiki commit hash to e16d2df 2021-12-23 17:14:24 +00:00
Renovate Bot
80c2091e34 Update ASF-ui commit hash to 4f7b927 2021-12-23 03:48:46 +00:00
Renovate Bot
9e522e7196 Update ASF-ui commit hash to 5149cd0 2021-12-22 20:23:20 +00:00
ArchiBot
335760c0bb Automatic translations update 2021-12-22 02:14:17 +00:00
Renovate Bot
e856ce8177 Update ASF-ui commit hash to 10a3ed9 2021-12-21 21:21:08 +00:00
Archi
16f02740d8 Handle AvatarHash NRE
https://github.com/SteamRE/SteamKit/pull/1067
2021-12-21 12:15:44 +01:00
Renovate Bot
7f5ada6dce Update ASF-ui commit hash to e11d32e 2021-12-21 04:35:34 +00:00
ArchiBot
65018efa7f Automatic translations update 2021-12-21 02:15:44 +00:00
Renovate Bot
3b87713fff Update wiki commit hash to 4f146ef 2021-12-20 18:30:39 +00:00
Archi
d141dce93d Bump 2021-12-20 18:41:29 +01:00
Archi
81a92d6781 Misc 2021-12-20 18:27:54 +01:00
Archi
f3d491611a Add MinFarmingDelayAfterBlock global config property 2021-12-20 18:10:46 +01:00
Renovate Bot
332d5d048c Update docker/login-action action to v1.12.0 2021-12-20 14:31:24 +00:00
Archi
11f8b6aae5 CI: Misc 2021-12-20 14:07:41 +01:00
Renovate Bot
6e5a02c380 Update docker/login-action action to v1.11.0 2021-12-20 11:35:12 +00:00
ArchiBot
22bbfe4e24 Automatic translations update 2021-12-20 02:13:57 +00:00
Archi
a2c278947d CI: Misc
| The command cannot remove the job because it does not exist or because it is a child job. Child jobs
     | can be removed only by removing the parent job.

I have no clue what is wrong with Windows and it's not critical anyway.
2021-12-18 17:50:45 +01:00
Renovate Bot
5db90e0eb8 Update ASF-ui commit hash to 99191bb 2021-12-18 02:44:55 +00:00
ArchiBot
1914f41ffe Automatic translations update 2021-12-18 02:14:18 +00:00
Renovate Bot
4754b3cbd9 Update crowdin/github-action action to v1.4.4 2021-12-17 16:59:06 +00:00
Renovate Bot
31acd4e7dc Update ASF-ui commit hash to 67c336f 2021-12-17 13:42:53 +00:00
Archi
f98d33bfa5 CI: Try to limit OOM on Windows 2021-12-17 14:09:55 +01:00
Archi
799b48d1b6 Revert "CI: Attemp to solve OOM on Windows"
This reverts commit 6444167ae4.
2021-12-17 14:09:25 +01:00
Archi
6444167ae4 CI: Attemp to solve OOM on Windows 2021-12-17 13:57:57 +01:00
Renovate Bot
543d03724d Update ASF-ui commit hash to 3c2bbaf 2021-12-17 04:13:06 +00:00
ArchiBot
cdd4ff9128 Automatic translations update 2021-12-17 02:30:16 +00:00
Renovate Bot
004c72127c Update ASF-ui commit hash to 78749d3 2021-12-16 16:41:36 +00:00
Archi
c08b2609fc Implement more precise time remaining for restricted accounts
It should be very close to reality now
2021-12-16 16:08:37 +01:00
Renovate Bot
eedb39e8df Update ASF-ui commit hash to 015b843 2021-12-16 02:59:12 +00:00
ArchiBot
379b9454ec Automatic translations update 2021-12-16 02:10:53 +00:00
Archi
02d0610a04 Final touches 2021-12-16 00:41:31 +01:00
Archi
f63723a157 Damn it 2021-12-16 00:38:38 +01:00
Archi
d59bccf1db Very important correction 2021-12-16 00:38:21 +01:00
Archi
2a734344bc Include examples of redacting in bug report 2021-12-16 00:35:36 +01:00
Archi
eb8946e480 Further update issue templates 2021-12-16 00:34:06 +01:00
Archi
55745c8093 Update issue templates 2021-12-16 00:18:03 +01:00
Archi
5a5a573e46 Bump 2021-12-15 20:41:41 +01:00
Archi
dc6968b371 Bump 2021-12-15 20:33:52 +01:00
Archi
692a0e0c9d Add Winter Sale 2021 to SalesBlacklist 2021-12-15 20:33:06 +01:00
Renovate Bot
c5839d3cbe Update actions/upload-artifact action to v2.3.1 2021-12-15 16:04:48 +00:00
Archi
bc3275fa9d Misc 2021-12-15 12:14:56 +01:00
ArchiBot
4c70a71072 Automatic translations update 2021-12-15 02:11:17 +00:00
Archi
fd2b9ff8d2 Update README.md 2021-12-15 00:00:10 +01:00
Archi
407b77428a Overkill 2021-12-14 23:58:57 +01:00
Archi
494dd69819 Update README.md 2021-12-14 23:58:30 +01:00
Archi
71f4e16603 Misc 2021-12-14 23:10:11 +01:00
Renovate Bot
1c0995426c Update ASF-ui commit hash to dbd7e04 2021-12-14 19:17:48 +00:00
Renovate Bot
82702647b4 Update dotnet monorepo to v3.1.22 2021-12-14 17:49:07 +00:00
Renovate Bot
b826a64f88 Update crowdin/github-action action to v1.4.3 2021-12-14 15:18:42 +00:00
Renovate Bot
d20c3257ed Update wiki commit hash to f2ed435 2021-12-14 13:55:50 +00:00
Renovate Bot
06622263c0 Update ASF-ui commit hash to c18c564 2021-12-14 03:24:07 +00:00
ArchiBot
73b3fe4c8a Automatic translations update 2021-12-14 02:13:18 +00:00
Archi
429b030021 Use alternative logic for public signing 2021-12-13 15:47:13 +01:00
ArchiBot
d60b932dfa Automatic translations update 2021-12-13 02:11:22 +00:00
Archi
5229f52f47 Use latest MSTest
Let's see if it finally works properly
2021-12-12 15:34:31 +01:00
ArchiBot
92a946c1cb Automatic translations update 2021-12-12 02:12:16 +00:00
Archi
03fc35dad0 Another try 2021-12-12 02:15:21 +01:00
Archi
225003c5d1 Try to fix netf, once again into the breach 2021-12-12 01:44:17 +01:00
Archi
4f598d5c8f Latest Rider cleanups 2021-12-12 01:12:54 +01:00
Archi
944df1cfc8 Decrease PICS refresh timer
We don't need to fetch info that often
2021-12-11 15:35:48 +01:00
Archi
e259f9e32f STD: Misc 2021-12-11 15:17:28 +01:00
Archi
c2cabfba49 STD: Add additional safeguards against depot keys corruption 2021-12-11 15:11:35 +01:00
Renovate Bot
dee8add183 Update ASF-ui commit hash to 45fe8ff 2021-12-11 02:47:31 +00:00
ArchiBot
06829beda4 Automatic translations update 2021-12-11 02:09:43 +00:00
Renovate Bot
2a35c82e0c Update ASF-ui commit hash to 28ad0e2 2021-12-10 19:28:45 +00:00
Renovate Bot
dbd8fa9877 Update ASF-ui commit hash to 43b3e47 2021-12-10 15:16:35 +00:00
Archi
d8a0d2f22d OCD 2021-12-10 15:00:59 +01:00
Archi
88996d1e35 Make ASF helper scripts aware of --service 2021-12-10 14:59:19 +01:00
Archi
78a88979dc Add service parameter to GET /Api/ASF 2021-12-10 14:22:49 +01:00
ArchiBot
2513bd4163 Automatic translations update 2021-12-10 02:09:39 +00:00
Archi
d9a5c30659 Closes #2472 2021-12-09 18:24:00 +01:00
ArchiBot
c60ea2ba3d Automatic translations update 2021-12-09 02:11:40 +00:00
Renovate Bot
7a07b6a22b Update crowdin/github-action action to v1.4.2 2021-12-08 22:08:30 +00:00
Renovate Bot
d89b6112dd Update ASF-ui commit hash to 3c31ac3 2021-12-08 20:47:55 +00:00
Archi
5d33bca611 Add IUpdateAware plugin interface 2021-12-08 19:48:59 +01:00
Renovate Bot
0f489f55e4 Update wiki commit hash to 9046b44 2021-12-08 16:41:15 +00:00
Archi
bf70f27449 Bump 2021-12-08 17:01:14 +01:00
Archi
0eab358af9 Plugins breaking: Convert all synchronous interface methods to Task
Okay, I wish we had uncovered it earlier as part of V5.2 but it has bitten us in the back just now, so I'm addressing it as part of monthly cycle instead.

Previously used void methods did not allow async operations in plugins in a "nice way". If plugin didn't require synchronization with the ASF and just minded its own business, it wasn't half bad as it could use async void signature. However, if plugin by any chance had to do something BEFORE ASF continued with the rest of the logic, it had to explicitly leave non-async void signature and call its async-capable stuff in synchronous manner (usually with Wait() or .Result), which is vastly suboptimal.

This was visible even in our STD plugin, which previously had (and still has) GlobalCache initialization in OnASFInit(). If that cache initialization took a bit longer time, STD would hit InvalidOperationException() in OnLicenseList() callback as global cache didn't load yet while we were already long past OnASFInit().

Therefore, I've decided to make a breaking change for a very good reason - all previous methods were converted to tasks, which allows from plugin to do one of three things:

- If plugin is async and requires synchronization (like STD), it can declare itself as async await, and do its awaits as-needed, and ASF will wait for those.
- If plugin is truly synchronous (and not just a synchronous signature with awful Wait() or .Result, see above), it can simply return Task.CompletedTask and has exactly the same logic.
- Finally, if plugin calls some async stuff but doesn't need ASF synchronization, it can "offload" itself from it by calling e.g. ASF's Utilities.InBackground() with whole logic, while returning Task.CompletedTask from the main method. This will allow it to effectively do what async void previously did, by just hooking into the process without intention of slowing it down.

All in all I'm confident this approach, while a bit counter-intuitive at first, will result in better compatibility between ASF and the plugins, as if I wanted to fix my STD issue right now without that breaking change, I'd have to actually call .Result on my async global cache loader function, which is utterly stupid if we can fix ASF to do the right thing instead.

This "approach" can be commonly found in some other libs with similar to ASF's event-hook behaviour, e.g. Discord.Net.

You'll sadly need to do some method signature changes in all of your plugins, as the core OnLoaded() was also changed. See the ones I did in SteamTokenDumperPlugin.cs if you need a practical example, and see ExamplePlugin.cs if you need further explanation.
2021-12-08 16:52:27 +01:00
Renovate Bot
3eae143c55 Update ASF-ui commit hash to aa650c8 2021-12-08 03:32:40 +00:00
ArchiBot
a33d46c85b Automatic translations update 2021-12-08 02:12:18 +00:00
Renovate Bot
4aa524f03e Update actions/upload-artifact action to v2.3.0 2021-12-08 00:01:56 +00:00
Renovate Bot
861e7ded16 Update actions/download-artifact action to v2.1.0 2021-12-07 21:18:02 +00:00
Archi
581d5167b9 Closes #2465 2021-12-07 21:34:46 +01:00
Archi
b9108742d4 Bump 2021-12-07 21:27:30 +01:00
Renovate Bot
86c19a2dce Update wiki commit hash to c8647e6 2021-12-07 13:54:40 +00:00
Renovate Bot
f64abc02ab Update ASF-ui commit hash to 9c9c415 2021-12-07 09:02:01 +00:00
Renovate Bot
d6e569c970 Update dependency System.Linq.Async to v5.1.0 2021-12-06 18:25:27 +00:00
Renovate Bot
a75d63cd7f Update ASF-ui commit hash to 09ad8a5 2021-12-06 17:01:23 +00:00
Renovate Bot
8bfc48d8dc Update ASF-ui commit hash to 468291f 2021-12-06 05:54:48 +00:00
ArchiBot
3de4069e3f Automatic translations update 2021-12-06 02:11:52 +00:00
Renovate Bot
874eb2d1c6 Update ASF-ui commit hash to 9a44864 2021-12-05 03:11:55 +00:00
ArchiBot
803d4554aa Automatic translations update 2021-12-05 02:14:37 +00:00
Renovate Bot
549ddb4271 Update dependency SteamKit2 to v2.4.0 2021-12-04 18:52:13 +00:00
renovate[bot]
9017c3970d Update dependency JustArchiNET.Madness to v3 (#2468)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-12-04 19:51:45 +01:00
Renovate Bot
1eecd8ace0 Update wiki commit hash to 758a267 2021-12-04 15:30:14 +00:00
Archi
31da584f75 Relax root warning
Even though the case is justified, we shouldn't render whole setups unsupported because of that, as running as root, while discouraged, does not directly affect the program stability.

This is especially true on Windows boxes where there is a lot of setups running with administrators by default and users are not even aware of that, I don't have a good fix for them (apart from reinstallation), and because I do not, I should not expect from them to supply cmd-line arg they don't even understand why.
2021-12-04 13:38:00 +01:00
Renovate Bot
1b1cdb8c3e Update ASF-ui commit hash to cff26d6 2021-12-04 10:33:11 +00:00
Renovate Bot
37e7f9f51c Update wiki commit hash to 835074b 2021-12-04 02:55:11 +00:00
ArchiBot
fc9dda13a0 Automatic translations update 2021-12-04 02:09:09 +00:00
Archi
94c214af96 Init emergency loggers to notify user about very early failures
"Very early failures" include exclusively lack of being able to navigate to given --path, as everything else is postponed until we get core loggers up and running. We should print the information to the user and abort the program at the minimum in this case.

Until now ASF silently ignored those errors and proceeded like usual, this is unwanted, if --path is wrong then it's on user to fix it.
2021-12-04 02:33:23 +01:00
Renovate Bot
a184fc555b Update wiki commit hash to 17b4272 2021-12-03 22:53:24 +00:00
Łukasz Domeradzki
ad2dae4faf Update RELEASE_TEMPLATE.md 2021-12-03 18:51:08 +01:00
Archi
aaf9cc67b3 Misc
60 days for lock-threads to ensure that issues and PRs get more or less a full month in stable release in case somebody would like to add something to them
2021-12-03 10:51:48 +01:00
ArchiBot
fe866554d6 Automatic translations update 2021-12-03 02:09:26 +00:00
Renovate Bot
97200da414 Update ASF-ui commit hash to 94a5e72 2021-12-02 15:44:18 +00:00
ArchiBot
876c332452 Automatic translations update 2021-12-01 02:13:09 +00:00
Renovate Bot
75bc0ed598 Update wiki commit hash to d204892 2021-11-30 10:00:20 +00:00
Renovate Bot
bcbc44cb1f Update ASF-ui commit hash to 2ec2c77 2021-11-30 07:53:18 +00:00
ArchiBot
db8b23031a Automatic translations update 2021-11-30 02:03:56 +00:00
Archi
586ad7c370 Madness 2.4.1 2021-11-30 00:29:21 +01:00
Archi
86867c8d99 Madness to the rescue! 2021-11-29 23:54:39 +01:00
Archi
6a824c2c6f Avoid verifying whether the special folder exists
We don't care at this stage, we'll fail when moving to given location
2021-11-29 23:43:19 +01:00
Archi
ac02495e80 Replace ~ in path with user's home location 2021-11-29 23:32:51 +01:00
Archi
67c5e1f7c4 Avoid creating www directory if it doesn't exist yet
Fixes nixOS packaging issues
2021-11-29 22:35:53 +01:00
Renovate Bot
85437774de Update ASF-ui commit hash to a04540b 2021-11-29 16:28:55 +00:00
Renovate Bot
8cb813a354 Update actions/setup-node action to v2.5.0 2021-11-29 11:37:54 +00:00
Archi
d64669d563 Closes #2459 2021-11-29 09:56:43 +01:00
ArchiBot
a049bf39d6 Automatic translations update 2021-11-29 02:09:15 +00:00
Renovate Bot
c191a85966 Update ASF-ui commit hash to 5fefa6b 2021-11-28 03:17:16 +00:00
ArchiBot
a5cd6314e4 Automatic translations update 2021-11-28 02:07:25 +00:00
Archi
90bf83cf48 Bump 2021-11-27 12:03:24 +01:00
Archi
d5233c52af Closes #2458 2021-11-27 11:57:34 +01:00
ArchiBot
f97dc5f512 Automatic translations update 2021-11-27 02:08:19 +00:00
Renovate Bot
844de630a6 Update ASF-ui commit hash to 88f7324 2021-11-26 02:21:37 +00:00
ArchiBot
b83c06aec9 Automatic translations update 2021-11-26 02:08:39 +00:00
Renovate Bot
9f1f8a1daf Update wiki commit hash to ec0bfbc 2021-11-25 17:29:48 +00:00
Archi
ab982604cf Misc
We already apply this logic further below, the bool switch is useless
2021-11-25 17:06:31 +01:00
Archi
b00e157349 Bump 2021-11-25 13:57:57 +01:00
Renovate Bot
4537571014 Update ASF-ui commit hash to 12c42ad 2021-11-25 03:07:52 +00:00
ArchiBot
a1df1ed446 Automatic translations update 2021-11-25 02:09:50 +00:00
Renovate Bot
cc99e9844c Update ASF-ui commit hash to 8fae9bb 2021-11-24 11:38:25 +00:00
Renovate Bot
c88a79327e Update actions/setup-dotnet action to v1.9.0 2021-11-24 08:58:05 +00:00
Renovate Bot
d75b5194bb Update ASF-ui commit hash to 8afd2bd 2021-11-24 03:52:45 +00:00
ArchiBot
aada326d3a Automatic translations update 2021-11-24 02:09:59 +00:00
Renovate Bot
1c9f50ab62 Update wiki commit hash to d1679ca 2021-11-23 21:30:56 +00:00
Archi
e68210cf2e Implement 2 additional crypto methods for Steam password
Inspiration by @legendofmiracles
2021-11-23 21:50:33 +01:00
Archi
b030755eb6 Remove !password command
This was one of the most counter-intuitive commands ever implemented, just use !encrypt
2021-11-23 21:14:40 +01:00
Archi
b64ad59eff Move checksum check a bit above
It's pointless to let user waste bandwidth on the full ASF asset if checksum is not available right away
2021-11-23 11:58:12 +01:00
Archi
8b0e71e72d Misc 2021-11-23 11:51:43 +01:00
Archi
18f62b714d Misc strings update 2021-11-23 11:50:20 +01:00
Archi
8f233acd32 Bump 2021-11-23 11:05:43 +01:00
Archi
5ba0ad8eed Hide https://github.com/dotnet/runtime/issues/60856 2021-11-23 10:47:33 +01:00
Renovate Bot
53c88725aa Update ASF-ui commit hash to d9f65f5 2021-11-23 04:03:32 +00:00
ArchiBot
24bc249f64 Automatic translations update 2021-11-23 02:09:01 +00:00
Renovate Bot
8712b01137 Update wiki commit hash to bcb0d98 2021-11-22 23:28:31 +00:00
Archi
3d1eab828b Misc 2021-11-22 23:52:17 +01:00
Archi
f0e213476d Misc rewrite 2021-11-22 22:47:52 +01:00
Archi
958c6bb704 Fix for OS-specific builds not being able to restart after update 2021-11-22 22:36:38 +01:00
Łukasz Domeradzki
d3737f0705 Update SUPPORT.md 2021-11-22 14:41:29 +01:00
ArchiBot
6497e2f3e9 Automatic translations update 2021-11-22 02:09:34 +00:00
Renovate Bot
da21fb355e Update wiki commit hash to 35053ef 2021-11-21 19:28:21 +00:00
Renovate Bot
30d448110f Update ASF-ui commit hash to 757fe21 2021-11-21 14:09:19 +00:00
ArchiBot
ece06abbc7 Automatic translations update 2021-11-21 02:10:14 +00:00
Archi
07348a5958 Treat system account as root on Windows 2021-11-20 22:19:28 +01:00
Renovate Bot
0db5d115db Update ASF-ui commit hash to 96337f1 2021-11-20 17:50:27 +00:00
ArchiBot
acfa02631d Automatic translations update 2021-11-20 02:08:08 +00:00
Archi
eb2c728361 Once again into the breach 2021-11-20 00:29:46 +01:00
Archi
a30c091387 Update madness, again 2021-11-20 00:05:02 +01:00
Archi
5a9d4d3f70 Update Madness, again and again 2021-11-19 23:45:49 +01:00
Archi
cdd35ad29d Address new trimming warnings 2021-11-19 22:54:26 +01:00
Archi
035d4b9ed8 Bump 2021-11-19 22:30:32 +01:00
ArchiBot
1f72a09282 Automatic translations update 2021-11-19 02:09:08 +00:00
Archi
0673b2e298 Closes #2420 2021-11-18 23:44:49 +01:00
Archi
666a04a8a8 Closes #2434
It'll be a miracle if I didn't make any mistake while refactoring this
2021-11-18 23:19:38 +01:00
Archi
efd5079c32 Closes #2421 2021-11-18 22:50:50 +01:00
renovate[bot]
7892f110ea Update dependency SteamKit2 to v2.4.0-Beta.1 (#2454)
* Update dependency SteamKit2 to v2.4.0-Beta.1

* Fix warnings

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Archi <JustArchi@JustArchi.net>
2021-11-18 22:47:54 +01:00
Archi
7b51cca934 Fix madness nullabilities 2021-11-18 22:02:40 +01:00
Archi
c709d529c1 Closes #2455 2021-11-18 21:33:06 +01:00
Łukasz Domeradzki
99569ee3fe Implement additional checksum verification for ASF builds (#2453)
* #2452

* Fix netf

* Apply feedback

* Misc

* Misc

* Apply feedback
2021-11-18 21:16:47 +01:00
Renovate Bot
b7aee818b4 Update dependency JustArchiNET.Madness to v2.1.0 2021-11-18 00:56:51 +00:00
Renovate Bot
7373d6148b Update ASF-ui commit hash to 4fe5f1f 2021-11-17 03:54:39 +00:00
ArchiBot
2c2a2016f6 Automatic translations update 2021-11-17 02:08:54 +00:00
Renovate Bot
e4b82a7714 Update dependency Humanizer to v2.13.14 2021-11-16 14:21:13 +00:00
Renovate Bot
67bc0b4eda Update ASF-ui commit hash to 4cc86b4 2021-11-15 21:18:41 +00:00
Archi
62effc4af1 STD: Postpone registering updated app change numbers
This is important as we don't want to miss a depot, by moving it below we ensure that all depot tasks succeeded before we mark appIDs as "finished with"
2021-11-15 19:53:46 +01:00
ArchiBot
19f63c94bf Automatic translations update 2021-11-15 02:08:52 +00:00
Renovate Bot
f6ede9b949 Update ASF-ui commit hash to 1a36ed4 2021-11-14 20:02:32 +00:00
Renovate Bot
cfacceddf9 Update wiki commit hash to 8c7a03e 2021-11-14 18:01:10 +00:00
Renovate Bot
f327409184 Update dependency JustArchiNET.Madness to v2.0.0 2021-11-13 13:46:59 +00:00
ArchiBot
1094049986 Automatic translations update 2021-11-13 02:08:19 +00:00
Archi
93b6ffdc23 Bump 2021-11-12 16:40:17 +01:00
Renovate Bot
f6033fb5cd Update wiki commit hash to b8bf5a4 2021-11-12 10:22:54 +00:00
ArchiBot
e8d0e870b9 Automatic translations update 2021-11-12 02:11:21 +00:00
Renovate Bot
7db932ecb7 Update ASF-ui commit hash to a452142 2021-11-12 00:04:02 +00:00
Archi
941b704c41 Satisfy netf 2021-11-11 23:43:49 +01:00
Archi
9575b58258 Madness to the rescue! 2021-11-11 23:36:48 +01:00
Archi
89a50674ec Use Environment.ProcessPath over calculating it ourselves
It's still required to be a static readonly field, as we need it calculated in-advance due to renames/deletion of original binary.

Also I'll probably need madness for this, sigh.
2021-11-11 22:56:50 +01:00
Archi
f36e5618a4 Revert "Change default farming order to hours ascending"
This reverts commit fefcf12f2f.
2021-11-11 22:52:03 +01:00
Archi
fefcf12f2f Change default farming order to hours ascending 2021-11-11 22:47:31 +01:00
Archi
ff85a88b42 Implement auto-migration of old bot database properties 2021-11-11 22:28:34 +01:00
Archi
c01a2ba863 Closes #2368
iq -> fq
ib -> fb
bl -> tb
2021-11-11 22:07:21 +01:00
Archi
66344a1a3d Fix netf again and again
Bless madness
2021-11-11 20:14:32 +01:00
Archi
260875da7e Use shared Random across ASF
This also removes PublicAPI of ASF's "shared random"
2021-11-11 19:34:21 +01:00
Archi
951d9dc99f Remove internal chmod +x after update
According to the .NET 6.0 ZipFile changes, .NET can now preserve chmod +x after extracting archive, so this "workaround" should no longer be needed
2021-11-11 18:41:52 +01:00
ArchiBot
0c8d77b3d9 Automatic translations update 2021-11-11 02:09:13 +00:00
Archi
f5f5c810dc Update README.md 2021-11-11 02:07:57 +01:00
Archi
8e045fdf71 Update README.md 2021-11-11 02:06:28 +01:00
Archi
71089a4953 Remove functions marked as obsolete 2021-11-11 01:57:08 +01:00
Archi
d1fc7ebb74 Use C# 10 string interpolation wherever possible 2021-11-11 01:53:34 +01:00
Archi
60376c4d93 Bring up new Madness alpha3 to fix netf 2021-11-11 01:17:49 +01:00
Archi
ff8074aeb6 Use simplified hashing functions 2021-11-11 00:41:38 +01:00
Archi
a9249a90f6 Remove TrimMode declaration
"link" should be default in .NET 6.0+
2021-11-11 00:22:03 +01:00
Archi
cc85b681f7 Bump 2021-11-10 22:37:13 +01:00
Archi
d1e8794fe3 I won 2021-11-10 22:05:08 +01:00
Archi
258ad17930 Apply Rider inspections
Want to bet at least one will break netf?
2021-11-10 21:54:15 +01:00
Archi
52b32315cc Update .editorconfig 2021-11-10 21:49:42 +01:00
Archi
d46e532458 Code cleanup 2021-11-10 21:47:42 +01:00
Archi
1e6ab11d9f Use file-scoped namespaces 2021-11-10 21:23:24 +01:00
Archi
95ad16e26d Revert "Set EnableCompressionInSingleFile"
This reverts commit ae3a60759a.
2021-11-10 21:05:39 +01:00
Sebastian Göls
7019445b84 Clean up #if hell a bit more (#2450)
* Clean up #if hell a bit more

* Add missing null checks
2021-11-10 20:36:17 +01:00
Łukasz Domeradzki
0850a261cb Add osx-arm64 ASF variant (#2451) 2021-11-10 20:36:09 +01:00
Archi
ae3a60759a Set EnableCompressionInSingleFile
The tradeoff is worth it for size in our case, people that don't want the startup time hit should be using generic package anyway
2021-11-10 20:01:10 +01:00
Archi
566be6e8c4 Use --self-contained and --no-self-contained as recommended by .NET 6.0 docs 2021-11-10 19:39:04 +01:00
Archi
9aaf8d8215 Refuse to run as root
hooray
2021-11-10 19:18:00 +01:00
Archi
0964cdac96 Minimize define hell
Skipping a 20 KB stub in OS-specific non-windows builds and omitting a few very fast if checks isn't worth the code quality degradation that involves all of the ifdef options.

ifdefs should be reserved for stuff that either doesn't compile whatsoever in some specific configurations (NETFRAMEWORK), or is required to make logical decisions based on the compiler input (e.g. DEBUG for detecting debugging builds or ASF_VARIANT_* for hardcoding the platform identifier to use for auto-updates)

In all other situations, we should use OperatingSystem if condition, even if it's equal to hitting them on the platforms that are unlikely to hit them.

And I say unlikely, because nothing stops me from downloading a win-x64 build and running it like a generic one on windows, what you gonna do?
2021-11-10 19:03:05 +01:00
Łukasz Domeradzki
e62234892a Fix SIGINT/SIGTERM no longer working in .NET 6.0 (#2449)
* Try to use new signals

* Fix for netf and windows

* Misc
2021-11-10 18:40:12 +01:00
Renovate Bot
55e3c064eb Update ASF-ui commit hash to 9eb53d6 2021-11-10 13:08:49 +00:00
Renovate Bot
32575e69ec Update ASF-ui commit hash to 7c9fb1a 2021-11-10 03:07:06 +00:00
ArchiBot
5cdeccc2ba Automatic translations update 2021-11-10 02:08:24 +00:00
Renovate Bot
695ffb4b1c Update ASF-ui commit hash to dca7c00 2021-11-09 23:07:32 +00:00
Archi
0977b359c2 Bump 2021-11-09 22:57:57 +01:00
Archi
c7443bef69 Update TrimmerRoots.xml 2021-11-09 22:49:17 +01:00
Archi
e238026121 Closes #2448 2021-11-09 22:43:13 +01:00
Archi
36d51e80d7 Bump 2021-11-09 22:15:35 +01:00
Archi
a4b5c9a6cc Bring back System.Collections.Immutable in trimmer roots
Still needed, sigh
2021-11-09 22:04:43 +01:00
Archi
b305af6f7f Bump 2021-11-09 21:34:20 +01:00
Archi
9a42a08624 Update TrimmerRoots for .NET 6.0
This is required to support custom plugins, I'm not sure if immutable collections are still needed, we'll find out after first alpha release
2021-11-09 21:10:42 +01:00
Archi
3eeafa9029 Bump 2021-11-09 19:03:49 +01:00
Sebastian Göls
fb4eb0b03a Use Madness via global usings (#2447)
* Use Madness via global usings

* Apply feedback

* Disable false positive null warning
2021-11-09 16:33:09 +01:00
Renovate Bot
41bfd8ad2b Update dependency JustArchiNET.Madness to v2.0.0-alpha2 2021-11-09 02:54:17 +00:00
ArchiBot
4a88549b76 Automatic translations update 2021-11-09 02:08:48 +00:00
Archi
f5b548f83b WebBrowser: use HTTP/3 2021-11-09 00:03:38 +01:00
Archi
35ca44ee01 Remove System.IO.FileSystem.AccessControl dependency
It's included in .NET 6.0 now
2021-11-08 23:45:19 +01:00
renovate[bot]
4929d35f11 Update dotnet monorepo to v6 (#2445)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-08 23:41:13 +01:00
Łukasz Domeradzki
0eee21360d .NET 6.0 (#2388)
* Bump non-source files to net6.0

* Correct .NET 6.0 warnings

* Correct CI

* Correct linking errors

Not much I can do about them ATM

* Remove TrimmerRoots

At least runtime is no longer needed for our STD plugin, not sure about the dictionary

* Correct cc.sh

* Revert "Remove TrimmerRoots"

This reverts commit 11f603d3d6.

* First round of cat & mice game

* Update Directory.Build.props

* Update Startup.cs

* Update Startup.cs

* Update Startup.cs

* Fix new warnings

* Update SDK

* Address netf error

* Update Directory.Packages.props
2021-11-08 23:41:02 +01:00
Renovate Bot
fd93f0cd03 Update dotnet monorepo to v3.1.21 2021-11-08 02:43:43 +00:00
ArchiBot
7e715e3478 Automatic translations update 2021-11-08 02:09:03 +00:00
Renovate Bot
417440340a Update wiki commit hash to 2e38a42 2021-11-07 16:32:30 +00:00
Renovate Bot
e9898a53a1 Update ASF-ui commit hash to 88fbcc1 2021-11-06 05:22:14 +00:00
ArchiBot
5200fa771c Automatic translations update 2021-11-06 02:07:47 +00:00
Renovate Bot
f078bacd96 Update ASF-ui commit hash to 53f9c4d 2021-11-05 21:49:44 +00:00
Renovate Bot
f3919651d1 Update wiki commit hash to 79b4d42 2021-11-05 10:38:16 +00:00
ArchiBot
63d6e33204 Automatic translations update 2021-11-05 02:09:03 +00:00
Renovate Bot
6ad64f405c Update ASF-ui commit hash to 943bcde 2021-11-04 02:41:55 +00:00
ArchiBot
62704be1f3 Automatic translations update 2021-11-04 02:10:42 +00:00
Renovate Bot
6d9dc5e54f Update actions/checkout action to v2.4.0 2021-11-03 04:51:31 +00:00
Renovate Bot
be2bdf0727 Update ASF-ui commit hash to 5aa26c6 2021-11-03 03:03:12 +00:00
ArchiBot
d323918441 Automatic translations update 2021-11-03 02:08:31 +00:00
Archi
7104c34d2e Update renovate.json5 2021-11-03 01:23:41 +01:00
Archi
b3fd8a21f4 Lower severity of default crypt key with protected data for current user
This type of encryption is already very secure, and we're using crypt key as salt only. Indeed, providing it increases security as hacker needs to know the salt in addition to the data, but it's not as critical as IPC hashing.
2021-11-02 23:36:12 +01:00
Archi
2909d4320a Do not bother checking IPC security when IPC itself is disabled 2021-11-02 23:33:35 +01:00
Renovate Bot
1886a32ab5 Update crowdin/github-action action to v1.4.1 2021-11-02 20:19:06 +00:00
Archi
b516980ad7 Misc 2021-11-02 21:17:25 +01:00
Archi
e59b45e63c Bump 2021-11-02 21:10:46 +01:00
Archi
cc59875769 Fix crash on machines that do not support qps-Ploc culture
E.g. mono
2021-11-02 21:02:32 +01:00
Renovate Bot
8699be4c25 Update ASF-ui commit hash to 3be4080 2021-11-02 04:39:18 +00:00
ArchiBot
13b28c38e3 Automatic translations update 2021-11-02 02:11:08 +00:00
Archi
3b5a07aab3 React appropriate to SIGINT (CTRL+C)
We're using custom console mechanism in order to implement interactive console, on top of that CTRL+C would not work when using IPC: true due to the fact that it doesn't trigger shutdown sequence, fix both of those problems by listening to SIGINT and exiting with code 130 upon receiving one

Tested on Linux
2021-11-01 18:55:58 +01:00
Archi
a77f0c1ad3 Update renovate.json5 2021-11-01 12:29:23 +01:00
Archi
b0b7184be5 Revert "Update mcr.microsoft.com/dotnet/aspnet Docker tag to v5.0"
This reverts commit 8533b79c22.
2021-11-01 12:15:10 +01:00
Renovate Bot
8533b79c22 Update mcr.microsoft.com/dotnet/aspnet Docker tag to v5.0 2021-11-01 06:21:22 +00:00
Renovate Bot
b1b7a54f5d Update ASF-ui commit hash to ec0d6d1 2021-11-01 04:06:15 +00:00
ArchiBot
6aba37d131 Automatic translations update 2021-11-01 02:30:26 +00:00
Archi
392fdcb633 Add DebugFast target
Useful for faster compilation before we get down to warnings and code improvements
2021-11-01 00:47:15 +01:00
ArchiBot
e0e015255b Automatic translations update 2021-10-31 02:10:21 +00:00
Renovate Bot
02af5d21d5 Update ASF-ui commit hash to 240819d 2021-10-30 08:58:31 +00:00
ArchiBot
775c137a2d Automatic translations update 2021-10-30 02:07:57 +00:00
Renovate Bot
3d7efcdcec Update ASF-ui commit hash to cbea108 2021-10-29 17:14:18 +00:00
Renovate Bot
fbbb50656d Update ASF-ui commit hash to 5400252 2021-10-29 02:44:13 +00:00
Renovate Bot
1c8b759624 Update dependency JetBrains.Annotations to v2021.3.0 2021-10-28 12:32:59 +00:00
Renovate Bot
a8f22e2833 Update ASF-ui commit hash to f0c3f8b 2021-10-28 03:54:13 +00:00
ArchiBot
04ab9a8e29 Automatic translations update 2021-10-28 02:08:32 +00:00
Renovate Bot
b175a088b9 Update ASF-ui commit hash to 872e990 2021-10-27 23:47:19 +00:00
Renovate Bot
9aaa4caed4 Update ASF-ui commit hash to 1d1142b 2021-10-27 16:18:05 +00:00
Archi
1cc49ba25c Bump 2021-10-27 13:35:38 +02:00
Archi
28d0068fdb Misc 2021-10-27 13:31:48 +02:00
Sebastian Göls
785b43781a Support lol-US locale for IPC requests (#2435)
* Support lol-US locale for IPC requests

* Support sr-CS as well

* Apply feedback

* Apply feedback and Rider cleanup

* Less allocations make everyone happy

* Apply feedback

* Explain why we're doing this stupidity

* Uppercase Windows/Linux compat fix

* Go back to earlier version
2021-10-27 13:15:56 +02:00
Renovate Bot
148472eda4 Update ASF-ui commit hash to 3120ae8 2021-10-27 09:39:35 +00:00
renovate[bot]
4268aac82f Update dependency Microsoft.NET.Test.Sdk to v17 (#2436)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-27 00:42:34 +02:00
Renovate Bot
f13ec6a227 Update ASF-ui commit hash to e81a44f 2021-10-26 20:17:55 +00:00
Archi
9265aee77c Update README.md 2021-10-26 18:41:55 +02:00
Renovate Bot
f5613d5967 Update ASF-ui commit hash to 089342d 2021-10-26 10:45:50 +00:00
Renovate Bot
4c52c43526 Update ASF-ui commit hash to eae058b 2021-10-25 22:32:26 +00:00
Sebastian Göls
d894f3a830 Localize IPC (#2431)
* Localize IPC

* Keep logs in one language

* Cleanup

* Revert changes that keep strings in one locale

* Order dependencies alphabetically

* Fix formatting (Thanks, Rider -.-)
2021-10-25 20:24:56 +02:00
Renovate Bot
ed0cb38147 Update ASF-ui commit hash to 4ff0081 2021-10-25 05:15:22 +00:00
ArchiBot
8e289e38b3 Automatic translations update 2021-10-25 02:11:41 +00:00
Archi
96fabd986c CI: Use ArchiBot GH token
This way releases should not be authored by "github-actions"
2021-10-24 19:08:08 +02:00
Archi
eb876aa4d1 Warn about incomplete translation also in our plugin 2021-10-24 18:40:26 +02:00
Renovate Bot
e874213d49 Update ASF-ui commit hash to 3f589c8 2021-10-24 04:09:06 +00:00
ArchiBot
d340dc57f3 Automatic translations update 2021-10-24 02:10:15 +00:00
Renovate Bot
df76864996 Update ASF-ui commit hash to 1e45657 2021-10-23 21:34:19 +00:00
Archi
d40132af4a Bump 2021-10-23 18:37:44 +02:00
Archi
03f3943f42 Misc 2021-10-23 18:36:54 +02:00
Renovate Bot
7a6018e011 Update ASF-ui commit hash to 1d7cc22 2021-10-23 04:35:34 +00:00
Sebastian Göls
f76771e1b6 Use string.Create(...) instead of new string(...) (#2430)
* Use string.Create(...) in MobileAuthenticator

* Make it work in NETFRAMEWORK
2021-10-22 22:01:15 +02:00
Renovate Bot
bc15da5cce Update dependency JustArchiNET.Madness to v1.4.0 2021-10-22 17:04:45 +00:00
Archi
67ddef7fa1 Change systemd dependencies
According to https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/ it's recommended for us to just use After=network.target to ensure that we're able to shutdown before systemd purges network connectivity
2021-10-22 10:44:06 +02:00
Renovate Bot
9e3c50cd50 Update ASF-ui commit hash to 2d52d2a 2021-10-22 04:43:04 +00:00
Renovate Bot
857e231f96 Update ASF-ui commit hash to 78012e7 2021-10-22 00:26:58 +00:00
Sebastian Göls
4a83ed5ccd Return error for 2FA token generation action (#2429) 2021-10-22 00:09:47 +02:00
Renovate Bot
00c6812e00 Update ASF-ui commit hash to 432b5a9 2021-10-21 20:54:10 +00:00
Renovate Bot
44503e82c6 Update ASF-ui commit hash to 93488f4 2021-10-21 03:19:57 +00:00
Renovate Bot
2d45de06c4 Update ASF-ui commit hash to cb04f9e 2021-10-20 20:17:06 +00:00
Renovate Bot
aeb85afb84 Update ASF-ui commit hash to 800a195 2021-10-20 03:08:39 +00:00
Renovate Bot
1f4967653c Update ASF-ui commit hash to eb5dfbe 2021-10-19 20:46:00 +00:00
Renovate Bot
731791e810 Update ASF-ui commit hash to 00efde1 2021-10-19 05:22:40 +00:00
ArchiBot
faa579535f Automatic translations update 2021-10-19 02:11:25 +00:00
Renovate Bot
08cc46c7ee Update ASF-ui commit hash to e6c8b29 2021-10-18 09:27:09 +00:00
ArchiBot
3051f30ab3 Automatic translations update 2021-10-18 02:15:59 +00:00
Renovate Bot
85766e5279 Update wiki commit hash to 265a4c2 2021-10-17 20:29:50 +00:00
Archi
618ff781eb Use ASF user account inside docker 2021-10-17 16:19:07 +02:00
ArchiBot
f022822e94 Automatic translations update 2021-10-17 02:09:42 +00:00
Renovate Bot
095859a727 Update ASF-ui commit hash to f545218 2021-10-17 00:11:18 +00:00
Archi
47696cc9a7 Misc 2021-10-17 01:18:28 +02:00
Archi
8580852f84 Avoid a breaking change 2021-10-17 01:05:22 +02:00
Archi
e702fca8bb Improve localization 2021-10-17 00:14:11 +02:00
Łukasz Domeradzki
bfbeb91633 Refuse to run as root without explicit ignore (#2427)
* Refuse to run as root without explicit ignore

* Apply feedback

* Detect docker a bit better

* Guard more OS parts behind ifdefs

* Fix warnings

* Further fixes
2021-10-17 00:11:04 +02:00
Renovate Bot
cfe88d59ec Update swashbuckle-aspnetcore monorepo to v6.2.3 2021-10-16 14:42:55 +00:00
ArchiBot
6f8ea30a90 Automatic translations update 2021-10-16 02:12:55 +00:00
Renovate Bot
f447b7a0ed Update ASF-ui commit hash to 4658e69 2021-10-15 23:02:52 +00:00
Archi
6450a11302 systemd service update 2021-10-15 23:36:16 +02:00
Renovate Bot
861d64e0dc Update actions/checkout action to v2.3.5 2021-10-15 17:42:53 +00:00
Renovate Bot
ead9da524d Update crazy-max/ghaction-import-gpg action to v4.1.0 2021-10-15 13:45:09 +00:00
ArchiBot
e883937056 Automatic translations update 2021-10-15 02:11:07 +00:00
Archi
2db957aec4 Misc 2021-10-15 01:11:02 +02:00
Archi
14052d9bbf Misc
Authenticator init section can't throw exception, and the message was pasted twice
2021-10-15 00:26:25 +02:00
Archi
002be87f24 Bump 2021-10-15 00:14:18 +02:00
Archi
d7e8f2785d Bump 2021-10-15 00:13:51 +02:00
Archi
d5489df46d Replace recursion in WebBrowser with a loop
More efficient on resources
2021-10-15 00:04:16 +02:00
Archi
df817f4ffc Cleanup and improvements after #2426 2021-10-14 23:55:35 +02:00
Sebastian Göls
f93143c133 Add ASF 2FA service endpoints (#2426)
* Add ASF 2FA service endpoints

* Misc.

* Move back to .../TwoFactorAuthentication

* Remove duplicate endpoints

* Remove now useless constructor

* Apply feedback
2021-10-14 23:41:12 +02:00
Renovate Bot
aedc9f0b21 Update ASF-ui commit hash to 699bb51 2021-10-14 09:53:28 +00:00
Renovate Bot
8d145dd98b Update ASF-ui commit hash to efff6c9 2021-10-14 02:22:51 +00:00
ArchiBot
8eade49343 Automatic translations update 2021-10-14 02:10:42 +00:00
Archi
14bab623b8 Actually be smarter 2021-10-14 00:25:41 +02:00
Archi
1456efc341 Update Utilities.cs 2021-10-14 00:13:19 +02:00
Archi
fc0c916137 Extend warnings for --cryptkey and lack of it
@Abrynos
2021-10-13 23:24:07 +02:00
Sebastian Göls
be027523ac Warn about insecure passwords (#2419)
* Add warnings about password security

* Warn about weak steam passwords even if they are encrypted

* Apply feedback

* Apply feedback

* Simplify code

* Move return criteria up a bit for increased performance

* Choose more fitting strings for localization

* Extract const value

* Fix incorrect null reference warning

* Switch prefix operator for postfix one

Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>

* Add tests

* Disable CA1724

The type name Utilities conflicts in whole or in part with the namespace name 'Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities'.

* Tell users why their password is considered weak

* Apply feedback

* Merge resource comments

* Misc.

* Use library for password testing and Run testing in background

* Clean up

* OncSeparate forbidden phrases forfor IPC passwords (once again)

* Additionally check encryption key

* Add comment about {0}

Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>
2021-10-13 21:44:48 +02:00
Renovate Bot
5af5e55cfe Update ASF-ui commit hash to 2b5fb60 2021-10-13 13:15:00 +00:00
Archi
9b8050e2b3 Don't stack threads in OnHeartBeat()
OnHeartBeat() is the least important function that is called every minute if required, not being able to enter the semaphore means some other action is already in progress, and while it makes sense for stuff like OnPersonaState() to wait for it, because it requires that, heartbeat can totally miss being executed, especially if the previous call didn't manage to finish yet.

It's not destructive as it is right now because tasks would be cancelled eventually when the server starts responding (due to if being removed right now), but it's entirely unnecessary burden.
2021-10-13 10:38:14 +02:00
Renovate Bot
0ff0fa8d95 Update ASF-ui commit hash to 16dfd96 2021-10-13 03:10:04 +00:00
ArchiBot
ff870d25cf Automatic translations update 2021-10-13 02:13:10 +00:00
Renovate Bot
818ad8cc09 Update wiki commit hash to f938786 2021-10-12 21:14:25 +00:00
Renovate Bot
618a01912b Update ASF-ui commit hash to 4213377 2021-10-12 19:48:28 +00:00
Renovate Bot
f3ffa792ea Update ASF-ui commit hash to 44857f2 2021-10-12 14:00:00 +00:00
Renovate Bot
976276a74a Update ASF-ui commit hash to b219ae6 2021-10-12 09:25:49 +00:00
Renovate Bot
86173f6022 Update dotnet monorepo to v3.1.20 2021-10-12 00:34:36 +00:00
Archi
ed033349ec Misc lessons from plugin template 2021-10-12 00:57:29 +02:00
Renovate Bot
c69a650bba Update ASF-ui commit hash to c77ea0a 2021-10-11 05:26:58 +00:00
ArchiBot
129cba80d6 Automatic translations update 2021-10-11 02:12:36 +00:00
Renovate Bot
8c1bff3d91 Update ASF-ui commit hash to 0fb5966 2021-10-10 07:57:32 +00:00
Renovate Bot
da6d2e1b13 Update ASF-ui commit hash to 0792d61 2021-10-09 22:43:57 +00:00
Renovate Bot
62d58a41a3 Update ASF-ui commit hash to e84a7ca 2021-10-09 15:10:35 +00:00
Archi
b48e73d939 Further systemd security hardening 2021-10-09 01:19:23 +02:00
Archi
454e9cdef4 Correct ReadWritePaths for systemd service
/tmp/ASF does not exist by default which will cause this rule to fail, we can limit ourselves to /tmp instead
2021-10-09 00:23:51 +02:00
Renovate Bot
78feda15ba Update ASF-ui commit hash to 028dcc1 2021-10-08 16:08:33 +00:00
Renovate Bot
1e5df17130 Update ASF-ui commit hash to 79a6be2 2021-10-07 19:27:30 +00:00
Renovate Bot
fdac946ecc Update ASF-ui commit hash to ea210b9 2021-10-07 01:45:20 +00:00
Renovate Bot
be18666764 Update ASF-ui commit hash to 3692f3c 2021-10-05 22:39:04 +00:00
Renovate Bot
b2f99c56cf Update ASF-ui commit hash to 3718aeb 2021-10-05 17:55:56 +00:00
Renovate Bot
11ff50048e Update ASF-ui commit hash to 55c1cc3 2021-10-05 08:47:23 +00:00
ArchiBot
c6872607c4 Automatic translations update 2021-10-05 02:09:16 +00:00
Renovate Bot
ff2c5af492 Update ASF-ui commit hash to 8364b59 2021-10-04 20:45:55 +00:00
Renovate Bot
cb2413a4d7 Update ASF-ui commit hash to dc165e5 2021-10-04 09:50:29 +00:00
ArchiBot
28c575a8a5 Automatic translations update 2021-10-04 02:13:42 +00:00
Renovate Bot
100f55fd58 Update ASF-ui commit hash to f53be9a 2021-10-02 02:12:59 +00:00
ArchiBot
2910d437d1 Automatic translations update 2021-10-02 02:08:29 +00:00
Renovate Bot
09d4dcbef6 Update ASF-ui commit hash to d2e5250 2021-10-01 21:17:45 +00:00
Renovate Bot
beaa383bb5 Update ASF-ui commit hash to 939eba3 2021-10-01 16:46:22 +00:00
Renovate Bot
4a24872dac Update ASF-ui commit hash to 152da38 2021-10-01 11:24:08 +00:00
ArchiBot
a7f414c0c7 Automatic translations update 2021-10-01 02:15:08 +00:00
Łukasz Domeradzki
965d3050a5 Address new Rider inspections (EAP) (#2418) 2021-10-01 01:21:09 +02:00
Renovate Bot
99205fa278 Update ASF-ui commit hash to 89a081a 2021-09-29 11:55:04 +00:00
Renovate Bot
1edaf69b2f Update ASF-ui commit hash to 030e095 2021-09-28 20:55:11 +00:00
ArchiBot
77675fb0e1 Automatic translations update 2021-09-28 02:10:16 +00:00
Renovate Bot
cd7736196f Update ASF-ui commit hash to dedb780 2021-09-27 22:34:16 +00:00
renovate[bot]
24c5aa1a92 Update dessant/lock-threads action to v3 (#2417)
* Update dessant/lock-threads action to v3

* Address breaking changes

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Archi <JustArchi@JustArchi.net>
2021-09-27 23:42:31 +02:00
Archi
7e9e90764b Use static lambdas wherever possible
Thanks Rider
2021-09-27 21:33:52 +02:00
Archi
f2d3a2a894 Use string interpolation wherever possible
Majority of cases still need to go through string.Format() due to localization requirements
2021-09-27 19:59:00 +02:00
Archi
de19010820 Enhance !addlicense command
We can actually provide very limited details about license activation with the newly implemented endpoint. While this won't provide valuable feedback for vast majority of cases, it can at least pretty reliably provide Fail/RateLimited, for example.

Huge thanks to @xPaw for the discovery and help
2021-09-27 19:50:18 +02:00
Renovate Bot
e79f41be42 Update ASF-ui commit hash to abf4ea8 2021-09-27 15:43:53 +00:00
Renovate Bot
196a056f06 Update actions/setup-node action to v2.4.1 2021-09-27 13:31:23 +00:00
Archi
eff08956bc Bump 2021-09-27 11:06:57 +02:00
Archi
79fb4da9a6 systemd updates 2021-09-27 11:05:39 +02:00
ArchiBot
13996748e5 Automatic translations update 2021-09-27 02:09:39 +00:00
Renovate Bot
40610d8af4 Update dependency Nito.AsyncEx.Coordination to v5.1.2 2021-09-26 02:50:00 +00:00
ArchiBot
7993e608b1 Automatic translations update 2021-09-26 02:08:43 +00:00
Archi
032dd6333d Implement first version of ArchiSteamFarm@.service
Service files for other variants will follow once this one is finished
2021-09-26 03:10:35 +02:00
Archi
1bfedbe5e8 Implement --service cmd-line arg 2021-09-26 01:05:38 +02:00
Archi
300c1c19ec Implement some deduplication for overlay
Including 3 same scripts for linux is overkill, osx and linux could also be aggregated to "unix" but too much work
2021-09-26 00:12:15 +02:00
Renovate Bot
c7fd08273b Update ASF-ui commit hash to 1975652 2021-09-25 20:59:52 +00:00
Archi
75a31410c0 Don't report unknown command on lack of response
Lack of response is actually access denied, not unknown command. Normally users don't get to this point because of !feedback, but in rare case when user has SOME permission, but insufficient one (like FamilySharing for !addlicense), he should get access denied response instead.

Also remove false logging of null response there, it's to be expected in this case.
2021-09-25 22:25:43 +02:00
Renovate Bot
49173a4b26 Update dependency Nito.AsyncEx.Coordination to v5.1.1 2021-09-25 16:52:41 +00:00
Renovate Bot
ea706b57e8 Update ASF-ui commit hash to 9b8a5da 2021-09-24 23:20:23 +00:00
ArchiBot
eeb1105aa7 Automatic translations update 2021-09-23 02:12:31 +00:00
Renovate Bot
d8975235d0 Update swashbuckle-aspnetcore monorepo to v6.2.2 2021-09-22 14:16:01 +00:00
Renovate Bot
7268fad3d0 Update ASF-ui commit hash to 986151a 2021-09-22 13:08:42 +00:00
Renovate Bot
de399d9bfe Update ASF-ui commit hash to bf90dc8 2021-09-22 03:41:42 +00:00
ArchiBot
383868351d Automatic translations update 2021-09-22 02:10:42 +00:00
Renovate Bot
93fc34a2fe Update ASF-ui commit hash to 3203296 2021-09-21 03:14:31 +00:00
Renovate Bot
a8ed029ed1 Update crowdin/github-action action to v1.4.0 2021-09-20 17:34:32 +00:00
Renovate Bot
24728bc6ea Update ASF-ui commit hash to 0c7b0ca 2021-09-18 23:32:05 +00:00
Renovate Bot
832d9e7be6 Update ASF-ui commit hash to d03a28c 2021-09-18 04:13:37 +00:00
Renovate Bot
1bc964b8d1 Update ASF-ui commit hash to 298f3aa 2021-09-17 13:43:47 +00:00
Renovate Bot
38e2bd10cd Update ASF-ui commit hash to 5e97827 2021-09-17 02:04:03 +00:00
Renovate Bot
4407c78268 Update ASF-ui commit hash to 8377e60 2021-09-16 23:38:53 +00:00
Renovate Bot
7fb5a470f0 Update ASF-ui commit hash to 47609a2 2021-09-16 18:10:15 +00:00
Renovate Bot
4ad81586df Update ASF-ui commit hash to 74c2511 2021-09-15 11:03:34 +00:00
Archi
c60d413a70 Misc 2021-09-15 12:43:25 +02:00
Renovate Bot
86ceaf2369 Update ASF-ui commit hash to 2090e2c 2021-09-15 05:04:30 +00:00
Renovate Bot
98721803eb Update ASF-ui commit hash to 86f7af8 2021-09-15 02:23:18 +00:00
ArchiBot
6aaec3ce8f Automatic translations update 2021-09-15 02:08:29 +00:00
Renovate Bot
f7f264466b Update dotnet monorepo to v3.1.19 2021-09-14 15:14:23 +00:00
Renovate Bot
023d01c7b8 Update ASF-ui commit hash to 38dcc6f 2021-09-14 11:44:14 +00:00
Renovate Bot
27f14802c4 Update actions/setup-dotnet action to v1.8.2 2021-09-14 08:47:16 +00:00
Renovate Bot
8e3c341910 Update ASF-ui commit hash to a11df3a 2021-09-14 04:38:20 +00:00
ArchiBot
d8384d2232 Automatic translations update 2021-09-14 02:08:23 +00:00
Archi
e90100a847 Misc 2021-09-13 15:36:41 +02:00
Renovate Bot
acff71c42c Update wiki commit hash to d1fc67a 2021-09-13 09:37:32 +00:00
ArchiBot
6a22332cc8 Automatic translations update 2021-09-13 02:08:30 +00:00
Archi
f0b7e46595 Update .editorconfig 2021-09-13 00:42:55 +02:00
Archi
c17074e98f Misc 2021-09-12 21:15:08 +02:00
ArchiBot
9c0582452f Automatic translations update 2021-09-12 02:09:51 +00:00
Renovate Bot
61d591ea3a Update ASF-ui commit hash to 1ace113 2021-09-10 12:03:40 +00:00
Renovate Bot
6e3e02c359 Update ASF-ui commit hash to 05efb5c 2021-09-10 03:21:12 +00:00
Renovate Bot
7c7c9750a6 Update ASF-ui commit hash to 8044edd 2021-09-09 21:30:22 +00:00
Renovate Bot
c8c96fd50d Update ASF-ui commit hash to 44ea48f 2021-09-08 15:28:57 +00:00
ArchiBot
668d4457e7 Automatic translations update 2021-09-07 02:07:17 +00:00
Renovate Bot
73367bb4fc Update ASF-ui commit hash to 158326f 2021-09-06 03:21:41 +00:00
ArchiBot
8fe6d98883 Automatic translations update 2021-09-06 02:10:51 +00:00
Archi
a1d0a84bac Bump 2021-09-05 23:30:09 +02:00
Archi
0a3ae316da Fix generic-netf update capability
For real this time.
2021-09-05 23:05:05 +02:00
Archi
a72d12ef75 Revert "CI: Don't apply zip_exec on generic-netf"
This reverts commit 63d25d06aa.
2021-09-05 22:16:40 +02:00
Archi
ad6ce38352 Bump 2021-09-05 21:24:22 +02:00
Archi
63d25d06aa CI: Don't apply zip_exec on generic-netf
Latest Mono (as of 6.12) is unable to extract zip archives with extended attributes that we're applying here
2021-09-05 20:49:14 +02:00
Renovate Bot
91a0bce535 Update ASF-ui commit hash to 817f0bd 2021-09-05 00:36:40 +00:00
renovate[bot]
d8838c4c80 Update crazy-max/ghaction-import-gpg action to v4 (#2412)
* Update crazy-max/ghaction-import-gpg action to v4

* Update

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Archi <JustArchi@JustArchi.net>
2021-09-05 02:36:12 +02:00
Renovate Bot
d9f5fe260f Update ASF-ui commit hash to 95f92be 2021-09-04 21:57:03 +00:00
Renovate Bot
de60a0ce72 Update ASF-ui commit hash to aeb113e 2021-09-04 13:02:49 +00:00
Renovate Bot
766b75321b Update ASF-ui commit hash to 792613e 2021-09-04 02:54:38 +00:00
Renovate Bot
8d0977d61e Update docker/setup-buildx-action action to v1.6.0 2021-09-03 21:14:34 +00:00
Renovate Bot
10deb785d5 Update ASF-ui commit hash to 4c3bc52 2021-09-03 16:49:27 +00:00
Renovate Bot
d21489ff8f Update ASF-ui commit hash to 4622d08 2021-09-03 02:43:03 +00:00
Renovate Bot
8e99f579d7 Update ASF-ui commit hash to 3f1c685 2021-09-02 23:16:21 +00:00
Renovate Bot
e90d002e77 Update ASF-ui commit hash to f0f30ad 2021-09-02 19:19:17 +00:00
Renovate Bot
604f76695a Update ASF-ui commit hash to b6b4379 2021-09-02 15:48:27 +00:00
Archi
0f0974a0c0 Bump 2021-09-02 13:59:17 +02:00
Archi
4935ec3187 Remove support for SystemEvents.TimeChanged
This causes excessive delay during ASF shutdown on Windows, and because it's completely optional for irrelevant feature, it's not worth the burden
2021-09-02 13:45:56 +02:00
Renovate Bot
ca03d68eb5 Update ASF-ui commit hash to fe78a1f 2021-09-02 03:57:50 +00:00
ArchiBot
8a7d0515e1 Automatic translations update 2021-09-02 02:07:17 +00:00
Renovate Bot
2808c5abe9 Update ASF-ui commit hash to 26ce2f0 2021-09-01 22:32:05 +00:00
Archi
4502b66544 Bump 2021-09-02 00:01:51 +02:00
Archi
2788df033b CI: Use windows artifacts on all platforms again
https://www.youtube.com/watch?v=nkUOACGtGfA
2021-09-01 23:55:12 +02:00
Archi
3545aae09c Misc
Thanks to Madness this ifdef is no longer required
2021-09-01 23:41:42 +02:00
Renovate Bot
64c371e122 Update ASF-ui commit hash to 9469767 2021-08-31 20:11:46 +00:00
Renovate Bot
3cdb4129ba Update swashbuckle-aspnetcore monorepo to v6.2.1 2021-08-31 03:01:19 +00:00
ArchiBot
20e6ba753d Automatic translations update 2021-08-31 02:07:11 +00:00
Renovate Bot
eb74265f2c Update ASF-ui commit hash to f2c4ea9 2021-08-30 03:26:22 +00:00
ArchiBot
84d4fd422a Automatic translations update 2021-08-30 02:07:09 +00:00
Archi
c08c9b2585 Misc 2021-08-30 01:29:27 +02:00
Archi
b32548d366 Remove explicit NLog dependency
NLog is included as part of NLog.Web.AspNetCore, we do not require a direct dependency.
2021-08-30 01:08:23 +02:00
Renovate Bot
c62582dc4a Update ASF-ui commit hash to ffca933 2021-08-29 02:30:10 +00:00
ArchiBot
f9391e493b Automatic translations update 2021-08-29 02:07:53 +00:00
Renovate Bot
e78fb5b1ec Update dependency NLog.Web.AspNetCore to v4.14.0 2021-08-28 22:24:20 +00:00
Renovate Bot
fbf8af754d Update ASF-ui commit hash to 9fd989f 2021-08-28 12:17:35 +00:00
Archi
4982bdbf12 Bump 2021-08-28 13:26:38 +02:00
ArchiBot
bfbe794ba2 Automatic translations update 2021-08-28 02:07:39 +00:00
Renovate Bot
541ee89126 Update dependency Markdig.Signed to v0.26.0 2021-08-27 21:14:32 +00:00
Renovate Bot
7ad756fbdc Update wiki commit hash to 426ae79 2021-08-27 19:27:29 +00:00
Renovate Bot
7cabbe090c Update ASF-ui commit hash to bf1fbf5 2021-08-27 08:40:20 +00:00
Renovate Bot
27c345a002 Update ASF-ui commit hash to 1bc9fc3 2021-08-27 04:06:11 +00:00
Renovate Bot
1fc822a985 Update ASF-ui commit hash to f8d47ba 2021-08-26 15:37:19 +00:00
Renovate Bot
90ea23ea8b Update crowdin/github-action action to v1.3.3 2021-08-26 11:21:18 +00:00
Archi
1f556f9264 Remove no longer needed workaround 2021-08-26 11:45:44 +02:00
Renovate Bot
01a7395105 Update ASF-ui commit hash to 6c31f9d 2021-08-26 02:37:47 +00:00
Sebastian Göls
9f26e72843 Remove two unnecessary context switches (#2407)
* Remove unnecessary context switch

* Remove unnecessary context switch
2021-08-25 13:08:29 +02:00
Renovate Bot
199e0e132d Update ASF-ui commit hash to 083b825 2021-08-25 03:03:25 +00:00
ArchiBot
2f5d4cff50 Automatic translations update 2021-08-25 02:07:14 +00:00
Renovate Bot
79c510cb92 Update ASF-ui commit hash to 06c48e6 2021-08-24 22:35:31 +00:00
Renovate Bot
ba50dd818d Update ASF-ui commit hash to 8a12cf9 2021-08-24 02:39:06 +00:00
ArchiBot
3f426546c2 Automatic translations update 2021-08-24 02:06:55 +00:00
Archi
69e2a3590c ApiAuthenticationMiddleware performance improvements
Previously we've used one semaphore per all ongoing authentication attempts, which is suboptimal given the existence of a lot of consumers, including ongoing (D)DoS or distributed bruteforce attack. ASF should be as resistant to that as possible, therefore it makes sense to replace the global semaphore with per-IP semaphore (actually task), that can control the access just as well, without stopping other consumers from accessing the same authentication process concurrently.
2021-08-24 01:37:14 +02:00
Renovate Bot
47855ca705 Update ASF-ui commit hash to b9379c5 2021-08-23 18:19:52 +00:00
Archi
5f5dcfbb99 Misc 2021-08-23 16:50:26 +02:00
Archi
882443711b Optimize ApiAuthenticationMiddleware for bruteforcing
We can favour bruteforcers by checking first if the client is even eligible for talking with us, this will (in a very negligible way) improve defense against common DoS.

Also rewrite Timer initialization while at it. This is internal class and we don't expect this middleware to be initialized more than once anyway.
2021-08-23 16:49:19 +02:00
Renovate Bot
e6a520b9f0 Update ASF-ui commit hash to bc7076c 2021-08-23 03:56:44 +00:00
ArchiBot
2e1694c65d Automatic translations update 2021-08-23 02:06:54 +00:00
Renovate Bot
a48b594398 Update ASF-ui commit hash to 604ee6e 2021-08-22 04:15:15 +00:00
ArchiBot
9ddc0da7a0 Automatic translations update 2021-08-22 02:07:17 +00:00
Renovate Bot
a7da072104 Update ASF-ui commit hash to 6ede396 2021-08-20 16:36:11 +00:00
Renovate Bot
03a962ae81 Update docker/build-push-action action to v2.7.0 2021-08-20 14:34:37 +00:00
Archi
3b5b00f5ee Update renovate.json5 2021-08-20 16:33:50 +02:00
Archi
3dec189e14 Misc 2021-08-20 14:31:12 +02:00
Renovate Bot
9fa55f31f9 Update ASF-ui commit hash to aae5768 2021-08-20 01:44:48 +00:00
Renovate Bot
2e03231ffc Update ASF-ui commit hash to ba80ecb 2021-08-19 16:11:11 +00:00
Vitaliya
06b11a3129 Disable unsafe for netf (#2405) 2021-08-19 17:50:18 +02:00
Renovate Bot
a2ffe2cbcd Update dependency JustArchiNET.Madness to v1.2.0 2021-08-19 12:26:33 +00:00
Renovate Bot
1216d83d51 Update ASF-ui commit hash to 35e64b3 2021-08-19 11:34:38 +00:00
Renovate Bot
f749998b04 Update dependency NLog to v4.7.11 2021-08-18 22:40:21 +00:00
Renovate Bot
37e8bdb9ee Update ASF-ui commit hash to 2603c4e 2021-08-18 13:00:24 +00:00
Renovate Bot
799f14fe5c Update ASF-ui commit hash to fe3ba10 2021-08-17 13:35:28 +00:00
Renovate Bot
a8f0456f1f Update dessant/lock-threads action to v2.1.2 2021-08-17 11:18:20 +00:00
Renovate Bot
a046f1c531 Update ASF-ui commit hash to f5551a8 2021-08-17 03:58:09 +00:00
Renovate Bot
6d18ea9c90 Update ASF-ui commit hash to c8e0500 2021-08-16 00:22:32 +00:00
Renovate Bot
9c168fa0ba Update ASF-ui commit hash to 6deb325 2021-08-15 03:27:23 +00:00
ArchiBot
cf9b90a73c Automatic translations update 2021-08-15 02:07:03 +00:00
Archi
31db8c2458 Misc 2021-08-14 21:25:29 +02:00
Renovate Bot
5857c76b52 Update wiki commit hash to f44105d 2021-08-14 11:41:21 +00:00
Archi
b456796d82 Bump 2021-08-14 11:47:54 +02:00
Renovate Bot
a3639c6d45 Update dotnet monorepo to v3.1.18 2021-08-13 22:58:38 +00:00
Archi
dff549bd27 Misc 2021-08-13 23:34:44 +02:00
Archi
648691a3eb Misc 2021-08-13 23:09:46 +02:00
Archi
926cbe7222 renovate.json cleanup
I hope it supports JSON5 comments
2021-08-13 23:07:25 +02:00
Łukasz Domeradzki
bb513921f1 Include ASF's windows-only parts only in generic and windows builds (#2404)
* Include ASF's windows-only parts only in generic and windows builds

* Apply Abry's note
2021-08-13 23:07:17 +02:00
Renovate Bot
1fda77a72d Update dependency Microsoft.NET.Test.Sdk to v16.11.0 2021-08-13 16:37:24 +00:00
Renovate Bot
d388aa733a Update ASF-ui commit hash to c457dc3 2021-08-13 14:55:46 +00:00
Renovate Bot
75ffee3dd0 Update crowdin/github-action action to v1.3.2 2021-08-13 12:41:14 +00:00
Renovate Bot
9d31765834 Update ASF-ui commit hash to 2cdd27f 2021-08-12 22:50:17 +00:00
Sebastian Göls
64efae942f Add pull request template (#2402)
* Add pull request template

* Put comments on single line and Add additional info section

* Change "here" to "below"
2021-08-12 18:13:52 +02:00
Archi
4dcd55d300 Add docker platforms to bug report template 2021-08-12 17:06:55 +02:00
Renovate Bot
5f81c6fdfa Update ASF-ui commit hash to 2295792 2021-08-12 04:03:10 +00:00
ArchiBot
4fd411fd56 Automatic translations update 2021-08-12 02:07:58 +00:00
Archi
e3711dde26 Handle LimitExceeded in SendMessagePart()
Thanks ArchiBot
2021-08-11 20:54:10 +02:00
Renovate Bot
19c66a019d Update wiki commit hash to b0d0b2e 2021-08-11 09:31:26 +00:00
Renovate Bot
f9cf549dfe Update ASF-ui commit hash to b9f61f4 2021-08-11 03:20:59 +00:00
ArchiBot
ae3010640e Automatic translations update 2021-08-11 02:07:32 +00:00
Renovate Bot
873c9e1d93 Update wiki commit hash to 902f3d2 2021-08-10 18:32:40 +00:00
Renovate Bot
c78094ec73 Update ASF-ui commit hash to ad2858c 2021-08-10 17:47:24 +00:00
Archi
b3d1f96824 Misc
> This call site is reachable on: 'FreeBSD', 'Linux', 'MacOS'. 'OS.UnixSetFileAccess(string, OS.EUnixPermission)' is only supported on: 'Linux', 'FreeBSD', 'OSX'.
2021-08-10 17:37:18 +02:00
Archi
de0ee3a497 Make use of latest Madness 1.1.0 features 2021-08-10 17:09:46 +02:00
Archi
350ef8c626 CI: Remove appveyor 2021-08-10 16:04:06 +02:00
Renovate Bot
8b84c3c934 Update ASF-ui commit hash to 0b64194 2021-08-10 10:46:15 +00:00
Archi
433732cd3b CI: Optimize GPG step in translations
Now it should be possible thanks to V3.2 and https://github.com/crazy-max/ghaction-import-gpg/issues/92
2021-08-10 12:17:02 +02:00
Archi
cd716f4cdf Update README.md 2021-08-10 12:04:34 +02:00
Archi
1bf3830973 Update README.md 2021-08-10 12:01:33 +02:00
Łukasz Domeradzki
f403e9c296 Better April Fools (#2401)
* Better April Fools

* Guard timer with lock

It's not thread-safe

* Address Abry note

* Add extra 100ms to the timer calculation
2021-08-10 11:43:36 +02:00
Renovate Bot
ccd54413c0 Update crazy-max/ghaction-import-gpg action to v3.2.0 2021-08-10 07:55:04 +00:00
Renovate Bot
4e39cd629a Update dependency JustArchiNET.Madness to v1.1.0 2021-08-09 18:07:13 +00:00
Renovate Bot
0bf8fe7296 Update ASF-ui commit hash to 83cd31f 2021-08-09 11:02:52 +00:00
Renovate Bot
2c390908b3 Update crowdin/github-action action to v1.3.1 2021-08-09 09:27:25 +00:00
Archi
443a1cb18d Update Bug-report.yml 2021-08-09 10:38:15 +02:00
Renovate Bot
9d6eaca298 Update ASF-ui commit hash to 976921e 2021-08-09 01:38:51 +00:00
Archi
46b2376849 Mark JetBrains.Annotations as private asset
Madness research shows that this doesn't need dll inclusion
2021-08-08 12:51:38 +02:00
ArchiBot
47c1b8fadd Automatic translations update 2021-08-08 02:07:33 +00:00
Renovate Bot
3702279786 Update ASF-ui commit hash to 4fe47f2 2021-08-07 21:35:30 +00:00
renovate[bot]
23162ce5fd Update dependency JustArchiNET.Madness to v1 (#2396)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-07 17:23:21 +02:00
Archi
436003546e Misc extra properties I've learned from Madness 2021-08-07 14:12:57 +02:00
Archi
911680d606 Remove IndexRange from explicit dependencies
Madness implicitly includes it already
2021-08-07 14:08:06 +02:00
Łukasz Domeradzki
9f281c6055 Embrace madness (#2394)
* Embrace madness

* Remove unused code

* Misc

* Address Abry's note

* Update for Madness 0.3.0
2021-08-07 14:03:46 +02:00
ArchiBot
b47f290512 Automatic translations update 2021-08-07 02:07:53 +00:00
Renovate Bot
dbe61e98ce Update ASF-ui commit hash to 48eb5b2 2021-08-06 12:45:49 +00:00
ArchiBot
b741f49372 Automatic translations update 2021-08-06 02:08:48 +00:00
Renovate Bot
68968c99ae Update ASF-ui commit hash to a48e08d 2021-08-05 20:24:00 +00:00
Archi
a9fd774988 Decrease netf code burden with SupportedOSPlatform 2021-08-05 21:31:12 +02:00
Archi
4eb4bc98ed Annotate remaining OS-specific parts of OS.cs 2021-08-05 21:24:23 +02:00
Archi
aa7fce300d Use new OperatingSystem API for OS detection
Supporting netf gets harder every day...
2021-08-05 21:17:42 +02:00
Renovate Bot
444c10b246 Update actions/setup-node action to v2.4.0 2021-08-05 18:03:29 +00:00
ArchiBot
5a6e3ed243 Automatic translations update 2021-08-05 02:08:38 +00:00
Renovate Bot
3b3ae1cf7c Update ASF-ui commit hash to 4fcb38b 2021-08-04 23:24:01 +00:00
Vitaliya
8cd57efe33 Don't serialize AdditionalProperties for Asset (#2393)
* Don't serialize AdditionalProperties for Asset

* Don't serialize additional properties in InventoryResponse as well
2021-08-05 00:31:37 +02:00
Renovate Bot
0cae6ab482 Update ASF-ui commit hash to de30191 2021-08-04 17:27:38 +00:00
Renovate Bot
92fdeeb6a4 Update actions/setup-node action to v2.3.2 2021-08-04 15:00:13 +00:00
Renovate Bot
5c5225f970 Update wiki commit hash to 09664ae 2021-08-04 11:28:14 +00:00
Renovate Bot
d87d700ae2 Update ASF-ui commit hash to 6bb1125 2021-08-04 03:41:27 +00:00
Renovate Bot
d3037fd981 Update ASF-ui commit hash to 3987367 2021-08-03 20:13:14 +00:00
Renovate Bot
953c7da7c2 Update actions/setup-node action to v2.3.1 2021-08-03 18:01:11 +00:00
Archi
4a4ebc7721 Misc 2021-08-03 19:45:11 +02:00
Renovate Bot
e2d16f3978 Update ASF-ui commit hash to af4c4dc 2021-08-03 03:59:03 +00:00
ArchiBot
6d18791e90 Automatic translations update 2021-08-03 02:15:32 +00:00
Renovate Bot
a6e0a155d2 Update ASF-ui commit hash to 97bdded 2021-08-02 20:40:31 +00:00
Archi
a1955bc881 Mark additional stuff for removal 2021-08-02 22:15:37 +02:00
Archi
93a8af71e9 Limit HashCode compatibility helper to netf 2021-08-02 21:51:21 +02:00
Archi
929d57709a Remove obsolete properties 2021-08-02 21:45:04 +02:00
Renovate Bot
f31da41ed8 Update Swashbuckle monorepo to v6.1.5 2021-08-02 16:06:11 +00:00
Renovate Bot
ab7859c619 Update ASF-ui commit hash to 80a21b3 2021-08-02 14:09:48 +00:00
Archi
208a4c048b Bump 2021-08-02 14:16:44 +02:00
Renovate Bot
4b88fe92aa Update wiki commit hash to 66f066a 2021-08-02 10:07:50 +00:00
ArchiBot
09201da6ec Automatic translations update 2021-08-02 02:08:51 +00:00
Archi
a83e3fa71a CI: Include SHA512SUMS as part of the release 2021-08-02 00:45:58 +02:00
Archi
6435b873af Bump 2021-08-01 22:36:07 +02:00
Archi
4b967ec7ba CI: Tie osx release as well 2021-08-01 22:03:43 +02:00
Archi
4b2e5f3178 CI: Tie final release builds to OS they match
Windows: generic-netf + win-x64
OS X: should have osx-x64, but doesn't seem to preserve chmod +x in the zip file
Linux: everything else
2021-08-01 21:31:49 +02:00
Archi
6ace2107a7 CI: Utilize zip_exec utility for chmod +x
We can aid non-windows users by adding chmod +x flag to appropriate executables directly in the zip file
2021-08-01 19:27:16 +02:00
Archi
d50e848f84 CI: Fix the check for secrets 2021-08-01 19:14:56 +02:00
Archi
d1f9ed6fec Bump 2021-08-01 17:06:14 +02:00
Renovate Bot
da194283ed Update ASF-ui commit hash to 6097c47 2021-08-01 11:18:32 +00:00
Archi
a40bb74d35 CI: Enable build also for debug configuration
With more and more conditionals depending on the configuration it starts making sense to build and test also debug build (it was broken for a short while)
2021-08-01 13:11:51 +02:00
Archi
4bdbbedfb1 CI hardening against errors 2021-08-01 13:00:14 +02:00
Archi
7971585a32 Enable private signing of CI builds 2021-08-01 12:56:08 +02:00
ArchiBot
4ac2774eaf Automatic translations update 2021-08-01 02:13:36 +00:00
Archi
b853bf0cf6 Back to sha1
Full sign doesn't work with sha256 (sad)
2021-07-31 19:19:32 +02:00
Archi
b3f490c328 Fix debug builds and use SHA256 public key 2021-07-31 18:47:18 +02:00
Archi
61ba422729 Move public sign to release builds 2021-07-31 17:44:32 +02:00
Renovate Bot
f087e0a9f9 Update ASF-ui commit hash to 8f89f83 2021-07-31 05:10:44 +00:00
Archi
04732ce37d Bump 2021-07-30 23:00:56 +02:00
Archi
2be8a8b2a8 Avoid a potential synchronous flush when serializing api authentication middleware response
Might result in something along:

2021-07-30 16:39:43|ArchiSteamFarm-6766|ERROR|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HMAJF2E5IVHB", Request id "0HMAJF2E5IVHB:00000005": An unhandled exception was thrown by the application. System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Flush()
   at Microsoft.AspNetCore.ResponseCaching.ResponseCachingStream.Flush()
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionBody.Flush()
   at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
   at System.IO.StreamWriter.Dispose(Boolean disposing)
   at System.IO.StreamWriter.Close()
   at Newtonsoft.Json.JsonTextWriter.CloseBufferAndWriter()
   at Newtonsoft.Json.JsonTextWriter.Close()
   at Newtonsoft.Json.JsonWriter.Dispose(Boolean disposing)
   at Newtonsoft.Json.JsonWriter.System.IDisposable.Dispose()
   at ArchiSteamFarm.IPC.WebUtilities.WriteJsonAsync[TValue](HttpResponse response, TValue value, JsonSerializerSettings jsonSerializerSettings)
   at ArchiSteamFarm.IPC.WebUtilities.WriteJsonAsync[TValue](HttpResponse response, TValue value, JsonSerializerSettings jsonSerializerSettings)
   at ArchiSteamFarm.IPC.Integration.ApiAuthenticationMiddleware.InvokeAsync(HttpContext context, IOptions`1 jsonOptions)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.ResponseCaching.ResponseCachingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
2021-07-30 20:54:52 +02:00
Archi
0b32862efe Use 4096-bit private key for public sign 2021-07-30 16:19:52 +02:00
Archi
77d34a2ac8 Enable public signing of ASF binaries
Pending tests whether this is going to have any drawbacks, but so far looks good
2021-07-30 16:09:46 +02:00
Archi
13ea9df707 Fix STD plugin for generic-netf
It failed because it couldn't find immutable collections, ASF doesn't have it either, it doesn't look like it's needed to satisfy the compiler anymore (SK2 includes it through protobuf-net)
2021-07-30 16:09:05 +02:00
renovate[bot]
97a5a94220 Update dependency SteamKit2 to v2.4.0-Alpha.3 (#2392)
* Update dependency SteamKit2 to v2.4.0-Alpha.3

* Address SK2 breaking changes

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Archi <JustArchi@JustArchi.net>
2021-07-30 15:33:29 +02:00
Renovate Bot
0d93706a49 Update ASF-ui commit hash to 0692fb3 2021-07-30 10:00:00 +00:00
ArchiBot
677fcfa198 Automatic translations update 2021-07-30 02:14:21 +00:00
Renovate Bot
8bdca25988 Update ASF-ui commit hash to 83e81ee 2021-07-29 23:48:46 +00:00
Archi
ef801eb15f Update Bug-report.yml 2021-07-30 01:33:41 +02:00
Archi
c1af6f545c Update Bug-report.yml 2021-07-30 01:33:03 +02:00
Archi
927ca76cca Update CODE_OF_CONDUCT.md 2021-07-30 01:07:11 +02:00
Archi
ad660d13cb Update CODE_OF_CONDUCT.md 2021-07-30 01:06:07 +02:00
Archi
18f701fce1 Use brand new wiki suggestion form 2021-07-30 00:56:32 +02:00
Archi
39d5f9c348 Use brand new enhancement idea form 2021-07-30 00:07:23 +02:00
Archi
1cca6caa0c Use brand new bug report form 2021-07-29 22:52:29 +02:00
Renovate Bot
460e57e137 Update ASF-ui commit hash to 285c9d7 2021-07-29 19:16:31 +00:00
Renovate Bot
ed613bcea9 Update ASF-ui commit hash to 5eef8fe 2021-07-29 02:25:53 +00:00
Sebastian Göls
5cbc25c44e Fix command mabadd working incorrectly (#2390)
* Fix command mabadd working incorrectly

* Do the same for mabrm
2021-07-28 22:43:17 +02:00
Sebastian Göls
862238f46b Delete old ISSUE_TEMPLATEmd (#2389) 2021-07-28 21:14:32 +02:00
Renovate Bot
0ae4a4c8b9 Update ASF-ui commit hash to 6b0000c 2021-07-28 02:26:16 +00:00
ArchiBot
9811d5973b Automatic translations update 2021-07-28 02:11:59 +00:00
Renovate Bot
256f8c4679 Update ASF-ui commit hash to bac7806 2021-07-27 18:48:32 +00:00
Archi
1df9c6290b Misc 2021-07-27 20:28:33 +02:00
Renovate Bot
5994030881 Update ASF-ui commit hash to 519a0a1 2021-07-27 02:41:28 +00:00
Renovate Bot
35d767f0b4 Update ASF-ui commit hash to bf22332 2021-07-27 00:48:24 +00:00
Renovate Bot
7f4c83ad49 Update ASF-ui commit hash to 2803ff0 2021-07-26 22:49:07 +00:00
ArchiBot
98c18756c5 Automatic translations update 2021-07-26 02:11:26 +00:00
Archi
0e2510528b Cleanup program initialization
Initial string[] args actually can't be null according to MSDN
2021-07-26 00:19:09 +02:00
Archi
ead2d460f5 Make program args case-insensitive
There is no good reason why it should be case-sensitive
2021-07-25 23:50:02 +02:00
Archi
3b31313c64 Bump 2021-07-25 23:41:08 +02:00
Archi
21c3e4a1a3 Refuse to handle Resart() when in --no-restart mode
While AutoRestart property in the config is a hint for ASF what it should be doing, --no-restart cmd-line argument is used in scripts and environments that must directly monitor the process, e.g. Docker. In such environments, we should refuse a call to Restart() action, as it's never feasible.
2021-07-25 23:35:56 +02:00
Renovate Bot
517ced1e14 Update ASF-ui commit hash to eb45427 2021-07-25 02:29:28 +00:00
ArchiBot
a875c2377f Automatic translations update 2021-07-25 02:09:17 +00:00
Renovate Bot
e805a3e7ad Update ASF-ui commit hash to 40f0e8f 2021-07-24 11:03:16 +00:00
Renovate Bot
93270be636 Update ASF-ui commit hash to f9af6f1 2021-07-24 03:12:20 +00:00
Renovate Bot
c923f93902 Update ASF-ui commit hash to 4f785cc 2021-07-23 22:30:06 +00:00
Archi
a785acf416 Use /bin/sh as entrypoint in docker containers
This way we can avoid a potential update process corruption which happened e.g. at https://github.com/JustArchiNET/ArchiSteamFarm/issues/2382
2021-07-24 00:29:22 +02:00
Sebastian Göls
66bbc56b02 Set end year of assembly copyright during build process (#2380)
* Set end year of assembly copyright during build process

* Use year property instead of string format

Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>

Co-authored-by: Łukasz Domeradzki <JustArchi@JustArchi.net>
2021-07-23 23:56:00 +02:00
Archi
6e285178d4 Misc 2021-07-23 20:35:48 +02:00
Łukasz Domeradzki
95b17d4e79 Update SECURITY.md 2021-07-23 20:30:58 +02:00
Łukasz Domeradzki
c97985c793 Update SECURITY.md 2021-07-23 20:30:49 +02:00
Renovate Bot
cee50b500d Update ASF-ui commit hash to 7c41ba5 2021-07-23 16:19:01 +00:00
Archi
b19303d4b6 Bump 2021-07-23 17:06:48 +02:00
Archi
7ff747fb6e Misc 2021-07-23 17:05:43 +02:00
Sebastian Göls
4b7edf388c Fix GlobalConfig update via IPC removing IPCPassword (#2379) 2021-07-23 16:54:20 +02:00
Renovate Bot
8b0052ad73 Update ASF-ui commit hash to 44f44e8 2021-07-23 12:01:02 +00:00
Renovate Bot
b6dd969fee Update crowdin/github-action action to v1.3.0 2021-07-23 08:40:09 +00:00
Archi
8ee77fb126 Bump 2021-07-22 18:43:51 +02:00
288 changed files with 32339 additions and 29047 deletions

View File

@@ -38,6 +38,7 @@ wiki
# |_____||_||_| |_| \__,_|/_/\_\
#
# https://github.com/github/gitignore/blob/master/Global/Linux.gitignore
# 4f7062e132d7f88e68ab737e64fef872bd3a491f
*~
@@ -60,6 +61,7 @@ wiki
# |_| |_| |_| \__,_| \___| \___/ |____/
#
# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# 2bb963b16a1957c865335e53537036c2e97399b5
# General
.DS_Store
@@ -96,6 +98,7 @@ Temporary Items
# |_|
#
# https://github.com/github/gitignore/blob/master/Global/MonoDevelop.gitignore
# e8b2e1a9cc7c9ca49bb05c20a4c4491b85feba6d
#User Specific
*.userprefs
@@ -113,6 +116,7 @@ test-results/
# \_/ |_||___/ \__,_| \__,_||_||____/ \__| \__,_| \__,_||_| \___/
#
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# 888439ee893d0097862f1d510585bd0e3cfd500f
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -510,6 +514,7 @@ FodyWeavers.xsd
# \_/\_/ |_||_| |_| \__,_| \___/ \_/\_/ |___/
#
# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# 5808b77453dec299d4daf8557b05a80be832a5b8
# Windows thumbnail cache files
Thumbs.db

View File

@@ -6,6 +6,7 @@ root = true
[*]
charset = utf-8
#file_header_template = · _ _ _ ____ _ _____\n / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___\n / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \\n / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |\n/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|\n\nCopyright 2015-2021 Łukasz "JustArchi" Domeradzki\nContact: JustArchi@JustArchi.net\n\nLicensed under the Apache License, Version 2.0 (the "License")\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
@@ -90,7 +91,7 @@ csharp_style_prefer_switch_expression = true:warning
csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable :warning
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
csharp_style_var_elsewhere = false:warning
@@ -159,7 +160,7 @@ dotnet_naming_style.t_pascal_case.required_prefix = T
# Symbol - almost everything
dotnet_naming_symbols.almost_everything.applicable_accessibilities = *
dotnet_naming_symbols.almost_everything.applicable_kinds = namespace,class,struct,property,method,field,event,delegate
dotnet_naming_symbols.almost_everything.applicable_kinds = namespace, class, struct, property, method, field, event, delegate
# Symbol - enums
dotnet_naming_symbols.enums.applicable_accessibilities = *
@@ -171,7 +172,7 @@ dotnet_naming_symbols.interfaces.applicable_kinds = interface
# Symbol - local parameters
dotnet_naming_symbols.local_parameters.applicable_accessibilities = *
dotnet_naming_symbols.local_parameters.applicable_kinds = parameter,local,local_function
dotnet_naming_symbols.local_parameters.applicable_kinds = parameter, local, local_function
# Symbol - type parameters
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
@@ -212,3 +213,16 @@ dotnet_style_qualification_for_property = false:warning
dotnet_style_readonly_field = true:warning
dotnet_style_require_accessibility_modifiers = always:warning
###############################
# JetBrains, IntelliJ/Rider #
###############################
[*.{csproj,props,xml}]
ij_xml_keep_blank_lines = 1
ij_xml_keep_line_breaks = false
ij_xml_keep_line_breaks_in_text = false
ij_xml_space_inside_empty_tag = true
[*.{json,json5}]
ij_json_keep_line_breaks = false

View File

@@ -1,3 +1,4 @@
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -6,7 +7,7 @@ We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
@@ -59,7 +60,7 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at ASF@JustArchi.net.
reported to the community leaders responsible for enforcement at **[ASF@JustArchi.net](mailto:ASF@JustArchi.net)**.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -114,14 +115,18 @@ the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@@ -1,15 +0,0 @@
<!--
I fully read and understood contributing guidelines of ASF available under https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md and I believe that my issue is valid - it requires a response from ASF development team, and not ASF support.
ASF GITHUB ISSUES IS NOT A PROPER PLACE FOR ANY TECHNICAL SUPPORT RELATED TO USING THE PROGRAM.
I UNDERSTAND THAT IF MY ISSUE IS NOT MEETING CONTRIBUTING GUIDELINES SPECIFIED ABOVE, ESPECIALLY IF IT'S A QUESTION OR TECHNICAL ISSUE THAT IS NOT RELATED TO ASF DEVELOPMENT IN ANY WAY, THEN IT WILL BE CLOSED AND LEFT UNANSWERED.
Feel free to remove our notice and fill the template below with your details.
-->
## Other
<!--
You're about to open a generic issue that isn't available as one of our issue templates. In most cases this is invalid and will be closed immediately - please ensure that your issue is really related to ASF development prior to posting it.
-->

197
.github/ISSUE_TEMPLATE/Bug-report.yml vendored Normal file
View File

@@ -0,0 +1,197 @@
name: 🐛 Bug report
description: Unexpected program behaviour that requires code correction
labels: ["🐛 Bug", "👀 Evaluation"]
body:
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Ensure that our bug report form is appropriate for you.
options:
- label: I read and understood ASF's **[Contributing guidelines](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md)**
required: true
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is a bug report
required: true
- label: I don't own more than **[10 accounts in total](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ#how-many-bots-can-i-run-with-asf)**
required: true
- label: I'm not using **[custom plugins](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Plugins)**
required: true
- label: This is not a **[question](https://github.com/JustArchiNET/ArchiSteamFarm/discussions)**
required: true
- label: This is not a **[technical issue](https://github.com/JustArchiNET/ArchiSteamFarm/discussions)**
required: true
- label: This is not **[ASF-ui problem](https://github.com/JustArchiNET/ASF-ui/issues/new/choose)**
required: true
- type: dropdown
id: version
attributes:
label: ASF version
description: If you're using a different version than the current **[stable](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** or **[pre-release](https://github.com/JustArchiNET/ArchiSteamFarm/releases)**, ensure that your bug report is reproducible on one of the below.
options:
- Latest stable release
- Latest pre-release
validations:
required: true
- type: dropdown
id: variant
attributes:
label: ASF variant
description: If you're using a different variant, ensure that your bug report is reproducible on one of the below.
options:
- docker-linux/amd64
- docker-linux/arm/v7
- docker-linux/arm64
- generic (with latest .NET runtime)
- generic-netf (with latest Mono runtime)
- linux-arm
- linux-arm64
- linux-x64
- osx-arm64
- osx-x64
- win-x64
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Bug description
description: Short explanation of what you were going to do, what did you want to accomplish?
placeholder: |
I tried to brew a coffee with ASF using `PUT /Api/Coffee` ASF API endpoint, but upon trying the program returned HTTP error: 418 I'm a teapot.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: What did you expect to happen?
placeholder: |
I expected my favourite latte macchiato in a cup put below the machine hosting ASF.
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: What happened instead?
placeholder: |
No coffee was brewed, and so I was forced to use a water dispenser instead :/.
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: |
Every command or action that happened after launching ASF, which leads to the bug.
If launching ASF with provided configs (below) is everything that is needed, then this section is not mandatory.
placeholder: |
1. Put cup below the machine hosting ASF.
2. Send `PUT /Api/Coffee` request selecting latte macchiato.
3. No coffee was brewed.
- type: textarea
id: possible-solution
attributes:
label: Possible reason/solution
description: |
Not mandatory, but you can suggest a fix/reason for the bug, if known to you.
If you observed something peculiar about your issue that could help us locate and fix the culprit, this is the right place.
placeholder: |
Perhaps no coffee was brewed because I was out of milk?
- type: dropdown
id: help
attributes:
label: Can you help us with this bug report?
description: |
ASF is offered for free and our resources are limited.
Helping us increases the chance of fixing the problem.
options:
- Yes, I can code the solution myself and send a pull request
- Somehow, I can test and offer feedback, but can't code
- No, I don't have time, skills or willings for any of that
validations:
required: true
- type: textarea
id: asf-log
attributes:
label: Full log.txt recorded during reproducing the problem
description: |
You can find `log.txt` file directly in ASF directory.
If the bug report doesn't come from the last run of the program, you can find logs from previous runs of the program in the `logs` directory instead.
If no `log.txt` was recorded due to crash at the very early stage, console output should be pasted instead.
placeholder: |
2021-12-16 00:20:43|dotnet-282887|INFO|ASF|InitCore() ArchiSteamFarm V5.2.1.2 (generic/6b492ffa-9927-431d-bae7-7360ab9968a9 | .NET 6.0.0-rtm.21522.10; debian-arm64; Linux 5.15.0-1-arm64 #1 SMP Debian 5.15.3-1 (2021-11-18))
2021-12-16 00:20:43|dotnet-282887|INFO|ASF|InitCore() Copyright © 2015-2021 JustArchiNET
2021-12-16 00:20:47|dotnet-282887|INFO|ASF|InitPlugins() Initializing Plugins...
2021-12-16 00:20:47|dotnet-282887|INFO|ASF|InitPlugins() Loading SteamTokenDumperPlugin V5.2.1.2...
2021-12-16 00:20:47|dotnet-282887|INFO|ASF|InitPlugins() SteamTokenDumperPlugin has been loaded successfully!
2021-12-16 00:20:47|dotnet-282887|INFO|ASF|UpdateAndRestart() ASF will automatically check for new versions every 1 day.
2021-12-16 00:20:52|dotnet-282887|INFO|ASF|Update() Checking for new version...
2021-12-16 00:20:54|dotnet-282887|INFO|ASF|Update() Local version: 5.2.1.2 | Remote version: 5.2.1.2
2021-12-16 00:20:54|dotnet-282887|INFO|ASF|Load() Loading STD global cache...
2021-12-16 00:20:56|dotnet-282887|INFO|ASF|Load() Validating STD global cache integrity...
2021-12-16 00:20:56|dotnet-282887|INFO|ASF|OnASFInit() SteamTokenDumperPlugin has been initialized successfully, thank you in advance for your help. The first submission will happen in approximately 47 minutes from now.
2021-12-16 00:20:57|dotnet-282887|INFO|ASF|Start() Starting IPC server...
2021-12-16 00:20:59|dotnet-282887|INFO|ASF|Start() IPC server ready!
render: text
validations:
required: true
- type: textarea
id: global-config
attributes:
label: Global ASF.json config file
description: |
The config can be found in `config` directory under `ASF.json` name.
You can leave this field empty if not using one.
Ensure that your config has redacted (but NOT removed) potentially-sensitive properties, such as:
- IPCPassword (recommended)
- SteamOwnerID (optionally)
- WebProxy (optionally, if exposing private details)
- WebProxyPassword (optionally, if exposing private details)
- WebProxyUsername (optionally, if exposing private details)
Redacting involves replacing sensitive details, for example with stars (***). You should refrain from removing config lines entirely, as their pure existence may be relevant and should be preserved.
placeholder: |
{
"AutoRestart": false,
"Headless": true,
"IPCPassword": "********",
"UpdateChannel": 2,
"SteamTokenDumperPluginEnabled": true
}
render: json
- type: textarea
id: bot-config
attributes:
label: BotName.json config of all affected bot instances
description: |
Bot config files can be found in `config` directory, ending with `json` extension.
You can leave this field empty if you don't have any defined.
Ensure that your config has redacted (but NOT removed) potentially-sensitive properties, such as:
- SteamLogin (mandatory)
- SteamPassword (mandatory)
- SteamMasterClanID (optionally)
- SteamParentalCode (optionally)
- SteamTradeToken (optionally)
- SteamUserPermissions (optionally, only SteamIDs)
Redacting involves replacing sensitive details, for example with stars (***). You should refrain from removing config lines entirely, as their pure existence may be relevant and should be preserved.
placeholder: |
{
"Enabled": true,
"SteamLogin": "********",
"SteamPassword": "********"
}
render: json
- type: textarea
id: additional-info
attributes:
label: Additional info
description: Everything else you consider worthy that we didn't ask for.
- type: markdown
attributes:
value: |
---
#### Thank you for taking the time to fill out this bug report.

View File

@@ -1,84 +0,0 @@
---
name: 🐛 Bug report
about: Unexpected program behaviour that needs code correction
title: ''
labels: 🐛 Bug, 👀 Evaluation
assignees: ''
---
<!--
I fully read and understood contributing guidelines of ASF available under https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md and I believe that my issue is valid - it requires a response from ASF development team, and not ASF support.
ASF GITHUB ISSUES IS NOT A PROPER PLACE FOR ANY TECHNICAL SUPPORT RELATED TO USING THE PROGRAM.
I UNDERSTAND THAT IF MY ISSUE IS NOT MEETING CONTRIBUTING GUIDELINES SPECIFIED ABOVE, ESPECIALLY IF IT'S A QUESTION OR TECHNICAL ISSUE THAT IS NOT RELATED TO ASF DEVELOPMENT IN ANY WAY, THEN IT WILL BE CLOSED AND LEFT UNANSWERED.
Feel free to remove our notice and fill the template below with your details.
-->
## Bug report
### Description
<!-- Short explanation of what you were going to do, what did you want to accomplish? -->
### Expected behavior
<!-- What did you expect to happen? -->
### Current behavior
<!-- What happened instead? -->
### Possible solution
<!-- Not mandatory, but you can suggest a fix/reason for the bug, if known to you. -->
### Steps to reproduce
<!-- Every command or action that happened after launching ASF, which leads to the bug. -->
<!-- If launching ASF with provided configs (below) is everything that is needed, then this section is not mandatory. -->
### Full log.txt recorded during reproducing the problem
```text
Paste here, in-between triple backtick tags
Ensure that your log is complete and was NOT recorded in Debug mode, as debug log may contain sensitive information that should not be shared publicly, as per our wiki statement. Standard ASF log does not include sensitive information.
```
### Global ASF.json config (if using one)
```json
Paste here, in-between triple backtick tags
Ensure that your config has redacted (but NOT removed) potentially-sensitive properties, such as:
- IPCPassword (recommended)
- SteamOwnerID (optionally)
- WebProxy (optionally, if exposing private details)
- WebProxyPassword (optionally, if exposing private details)
- WebProxyUsername (optionally, if exposing private details)
Redacting involves replacing sensitive details, for example with stars (***). You should refrain from removing config lines entirely, as their pure existence may be relevant and should be preserved.
```
### BotName.json config of all affected bot instances (if more than one)
```json
Paste here, in-between triple backtick tags
Ensure that your config has redacted (but NOT removed) potentially-sensitive properties, such as:
- SteamLogin (mandatory)
- SteamPassword (mandatory)
- SteamMasterClanID (optionally)
- SteamParentalCode (optionally)
- SteamTradeToken (optionally)
- SteamUserPermissions (optionally, only SteamIDs)
Redacting involves replacing sensitive details, for example with stars (***). You should refrain from removing config lines entirely, as their pure existence may be relevant and should be preserved.
```
### Additional info
<!-- Everything else you consider worthy that we didn't ask for. -->

View File

@@ -0,0 +1,81 @@
name: ✨ Enhancement idea
description: General idea for improving the project
labels: ["✨ Enhancement", "👀 Evaluation"]
body:
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Ensure that our enhancement idea form is appropriate for you.
options:
- label: I read and understood ASF's **[Contributing guidelines](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md)**
required: true
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is an enhancement idea
required: true
- label: My idea doesn't duplicate existing ASF functionality described on the **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)**
required: true
- label: I believe that my idea falls into ASF's scope and should be offered as part of ASF built-in functionality
required: true
- label: My idea doesn't violate the **[Steam Subscriber Agreement](https://store.steampowered.com/subscriber_agreement)**
required: true
- label: My idea doesn't violate the **[Steam Online Conduct](https://store.steampowered.com/online_conduct)**
required: true
- label: This is not **[ASF-ui suggestion](https://github.com/JustArchiNET/ASF-ui/issues/new/choose)**
required: true
- type: textarea
id: enhancement-purpose
attributes:
label: Enhancement purpose
description: |
Purpose of the enhancement - if it solves some problem, precise in particular which. If it benefits the program in some other way, precise in particular why.
Present the underlying reason why this enhancement makes sense, and what is the context of it.
placeholder: |
As of today ASF offers variety of beverages, such as latte macchiato or cappuccino. I'd appreciate if ASF offered some no-milk options as well, for example espresso or ristretto.
I believe it'd further improve the program offering the users wider selection, which is very convenient.
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
description: What would you like to see as a solution to the purpose specified by you above?
placeholder: |
Simply add an option to brew some no-milk types of coffee. The existing logic is fine, we just need wider choice!
validations:
required: true
- type: textarea
id: why-existing-not-sufficient
attributes:
label: Why currently available solutions are not sufficient?
description: |
Evaluate the existing solutions in regards to your requirements.
If something you're suggesting is already possible, then explain to us why the currently available solutions are not sufficient.
If it's not possible yet, then explain to us why it should be.
placeholder: |
I'm allergic to milk, there is currently no option to pick a beverage that doesn't include it.
Temporarily I'm switching cup mid-brewing as a workaround, but that is suboptimal considering the milk wasted.
validations:
required: true
- type: dropdown
id: help
attributes:
label: Can you help us with this enhancement idea?
description: |
ASF is offered for free and our resources are limited.
Helping us increases the chance of making it happen.
options:
- Yes, I can code the solution myself and send a pull request
- Somehow, I can test and offer feedback, but can't code
- No, I don't have time, skills or willings for any of that
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional info
description: Everything else you consider worthy that we didn't ask for.
- type: markdown
attributes:
value: |
---
#### Thank you for taking the time to fill out this enhancement idea.

View File

@@ -1,44 +0,0 @@
---
name: ✨ Enhancement idea
about: General idea for improving the project
title: ''
labels: ✨ Enhancement, 👀 Evaluation
assignees: ''
---
<!--
I fully read and understood contributing guidelines of ASF available under https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md and I believe that my issue is valid - it requires a response from ASF development team, and not ASF support.
ASF GITHUB ISSUES IS NOT A PROPER PLACE FOR ANY TECHNICAL SUPPORT RELATED TO USING THE PROGRAM.
I UNDERSTAND THAT IF MY ISSUE IS NOT MEETING CONTRIBUTING GUIDELINES SPECIFIED ABOVE, ESPECIALLY IF IT'S A QUESTION OR TECHNICAL ISSUE THAT IS NOT RELATED TO ASF DEVELOPMENT IN ANY WAY, THEN IT WILL BE CLOSED AND LEFT UNANSWERED.
Feel free to remove our notice and fill the template below with your details.
-->
## Enhancement
### Purpose
<!-- Purpose of the enhancement - if it solves some problem, precise in particular what. If it benefits the program in some other way, precise in particular why. Present the underlying reason why this enhancement makes sense, and what is the context of it. -->
### Solution
<!-- What would you like to see as a solution to the purpose specified by you above? What would work for you? -->
### Why currently available solutions are not sufficient?
<!-- If something you're suggesting is already possible, then explain to us why currently available solutions are not sufficient. If it's not possible yet, then explain to us why it should be. -->
### Does your suggestion fall into ASF scope?
<!-- Is ASF really the proper tool to include your enhancement in the first place? Is it connected with idling Steam cards? -->
### Is your suggestion abiding to Steam guidelines?
<!-- If not, it will not be considered. Please make sure that you're not suggesting anything potentially unwanted, botting Steam Market is just a single example of such thing - https://store.steampowered.com/subscriber_agreement / https://store.steampowered.com/online_conduct -->
### Additional info
<!-- Everything else you consider worthy that we didn't ask for. -->

View File

@@ -0,0 +1,94 @@
name: 📕 Wiki suggestion
description: All issues related to our wiki documentation, mainly corrections and ideas
labels: ["📕 Wiki", "👀 Evaluation"]
body:
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Ensure that our wiki suggestion form is appropriate for you.
options:
- label: I read and understood ASF's **[Contributing guidelines](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md)**
required: true
- label: I also read **[Setting-up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)** and **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)**, I don't need **[help](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/SUPPORT.md)**, this is a wiki suggestion
required: true
- label: This is not a **[translation issue](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Localization)**
required: true
- label: This is not **[ASF-ui wiki suggestion](https://github.com/JustArchiNET/ASF-ui/issues/new/choose)**
required: true
- type: input
id: wiki-page
attributes:
label: Wiki page
description: |
If this is a suggestion regarding an existing wiki page, please link it for reference.
If the wiki page doesn't exist, suggest its title.
placeholder: https://github.com/JustArchiNET/ArchiSteamFarm/wiki/???
validations:
required: true
- type: textarea
id: issue
attributes:
label: The issue
description: |
Please specify your issue in regards to our wiki documentation.
If you're reporting a mistake/correction, state what is wrong.
If you're suggesting an idea, explain the details.
placeholder: |
As of today the wiki doesn't explain how to sing famous song composed by Rick Astley - Never Gonna Give You Up.
I'm sick of googling the lyrics every time I'm opening a complaint on your GitHub, so please consider just adding it along the other stuff.
validations:
required: true
- type: textarea
id: wrong-text
attributes:
label: Wrong text
description: |
The existing text on the wiki which you classify as wrong.
If you're suggesting a new page, paragraph or other addition to the wiki, then this section is not mandatory.
placeholder: |
Lack of song lyrics is what's wrong!
render: markdown
- type: textarea
id: suggested-improvement
attributes:
label: Suggested improvement
description: |
The new or corrected text that would satisfy your issue stated above.
You may use **[markdown](https://guides.github.com/features/mastering-markdown)** for formatting.
placeholder: |
# Never Gonna Give You Up by Rick Astley
## Verse 1
We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
## Pre-Chorus
I just wanna tell you how I'm feeling
Gotta make you understand
## Chorus
Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you
## More
See **[full version](https://www.youtube.com/watch?v=dQw4w9WgXcQ)**.
render: markdown
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional info
description: Everything else you consider worthy that we didn't ask for.
- type: markdown
attributes:
value: |
---
#### Thank you for taking the time to fill out this wiki suggestion.

View File

@@ -1,36 +0,0 @@
---
name: 📕 Wiki correction
about: All issues related to our wiki documentation, mainly corrections and ideas
title: ''
labels: 📕 Wiki, 👀 Evaluation
assignees: ''
---
<!--
I fully read and understood contributing guidelines of ASF available under https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md and I believe that my issue is valid - it requires a response from ASF development team, and not ASF support.
ASF GITHUB ISSUES IS NOT A PROPER PLACE FOR ANY TECHNICAL SUPPORT RELATED TO USING THE PROGRAM.
I UNDERSTAND THAT IF MY ISSUE IS NOT MEETING CONTRIBUTING GUIDELINES SPECIFIED ABOVE, ESPECIALLY IF IT'S A QUESTION OR TECHNICAL ISSUE THAT IS NOT RELATED TO ASF DEVELOPMENT IN ANY WAY, THEN IT WILL BE CLOSED AND LEFT UNANSWERED.
Feel free to remove our notice and fill the template below with your details.
-->
## Wiki correction
### Wiki page
<!-- Please link the appropriate URL of the wiki page with the issue, if applicable. -->
### The issue
<!-- Please explain the problem with the current state of things. If you're reporting a mistake/correction, state which one, if you're suggesting an idea, explain the details. -->
### Possible solution
<!-- Not mandatory, but if you have an idea how to address the issue explained by you above, e.g. helpful sentences, words or resources, you can include them here. -->
### Additional info
<!-- Everything else you consider worthy that we didn't ask for. -->

28
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,28 @@
## Checklist
<!-- Put an `x` in all the boxes that apply -->
- [ ] I read and understood the **[Contributing Guidelines](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md)**.
- [ ] This is not a **[duplicate](https://github.com/JustArchiNET/ArchiSteamFarm/pulls)** of an existing merge request.
- [ ] I believe this falls into the scope of the project and should be part of the built-in functionality.
- [ ] My code follows the **[code style](https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/.github/CONTRIBUTING.md#code-style)** of this project.
- [ ] I have added tests to cover my changes, wherever they are necessary.
- [ ] All new and existing tests pass.
## Changes
### New functionality
<!-- Please describe below, what new functionality was added. -->
### Changed functionality
<!-- Please describe below, what old functionality was changed. -->
### Removed functionality
<!-- Please describe below, what old functionality was removed. Make sure to mention what it was replaced with or how everything that was previously achievable still is. -->
## Additional info
<!-- Everything else you consider note-worthy that we didn't ask for. -->

View File

@@ -12,6 +12,6 @@ This is automated GitHub deployment, human-readable changelog should be availabl
### Support
ASF is available for free, this release was made possible thanks to the people that decided to support the project. If you're grateful for what we're doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even $1 is highly appreciated and shows that you care. Thank you!
ASF is available for free, this release was made possible thanks to the people that decided to support the project. If you're grateful for what we're doing, please consider a donation. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even $1 is highly appreciated and shows that you care. Thank you!
[![GitHub sponsor](https://img.shields.io/badge/GitHub-sponsor-ea4aaa.svg?logo=github-sponsors)](https://github.com/sponsors/JustArchi) [![Patreon support](https://img.shields.io/badge/Patreon-support-f96854.svg?logo=patreon)](https://www.patreon.com/JustArchi) [![Crypto donate](https://img.shields.io/badge/Crypto-donate-f7931a.svg?logo=bitcoin)](https://commerce.coinbase.com/checkout/0c23b844-c51b-45f4-9135-8db7c6fcf98e) [![PayPal.me donate](https://img.shields.io/badge/PayPal.me-donate-00457c.svg?logo=paypal)](https://paypal.me/JustArchi) [![PayPal donate](https://img.shields.io/badge/PayPal-donate-00457c.svg?logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Revolut donate](https://img.shields.io/badge/Revolut-donate-0075eb.svg?logo=revolut)](https://pay.revolut.com/profile/ukaszyxm) [![Steam donate](https://img.shields.io/badge/Steam-donate-000000.svg?logo=steam)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)

6
.github/SECURITY.md vendored
View File

@@ -8,6 +8,12 @@ We support **[the latest stable](https://github.com/JustArchiNET/ArchiSteamFarm/
---
## Security advisories
We announce security advisories for our program on **[GitHub](https://github.com/JustArchiNET/ArchiSteamFarm/security/advisories)**. Every entry includes detailed information about the security vulnerability it describes, especially affected versions, attack vectors, fixed versions as well as possible workarounds (if any).
---
## Reporting a vulnerability
We're doing our best to protect our community from all harm, therefore we take security vulnerabilities very seriously.

2
.github/SUPPORT.md vendored
View File

@@ -4,4 +4,4 @@ Our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** is the offic
We also have three independent support channels dedicated to our ASF users, in case you couldn't manage to solve the issue yourself. We answer all support and technical matters in our **[GitHub discussions](https://github.com/JustArchiNET/ArchiSteamFarm/discussions/categories/support)**, **[Steam group](https://steamcommunity.com/groups/archiasf/discussions/1)**, and on our **[Discord server](https://discord.gg/hSQgt8j)**. You're free to use the support channel that matches your preferences, although keep in mind that you have a higher chance solving your issue on the GitHub or Steam, where we're doing our best to answer all questions that couldn't be answered by our community itself (as opposed to Discord server where we're not active 24/7 and therefore not always able to answer).
GitHub issues are being used solely for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub issues is **not** a general support channel, it's dedicated exclusively to ASF development and we're not answering common ASF matters there, as we have appropriate support channels (mentioned above) for that. Common matters include not only general questions or issues that are obviously related to program usage, but also users reporting "bugs" that are clearly considered intended behaviour coming for example (and mainly) from misconfiguration or lack of understanding how the program works. If you're not sure whether your matter relates to ASF development or not, especially if you're not sure if it's a bug or intended behaviour, we recommend to use a support channel instead, where we'll answer you in calm atmosphere and forward your matter to GitHub if deemed appropriate. Invalid GitHub issues will be closed immediately and won't be answered.
GitHub **issues** (unlike discussions), are being used solely for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub issues is **not** a general support channel, it's dedicated exclusively to ASF development and we're not answering common ASF matters there, as we have appropriate support channels (mentioned above) for that. Common matters include not only general questions or issues that are obviously related to program usage, but also users reporting "bugs" that are clearly considered intended behaviour coming for example (and mainly) from misconfiguration or lack of understanding how the program works. If you're not sure whether your matter relates to ASF development or not, especially if you're not sure if it's a bug or intended behaviour, we recommend to use a support channel instead, where we'll answer you in calm atmosphere and forward your matter to GitHub if deemed appropriate. Invalid GitHub issues will be closed immediately and won't be answered.

276
.github/appveyor.yml vendored
View File

@@ -1,276 +0,0 @@
version: '{build}-{branch}'
pull_requests:
do_not_increment_build_number: true
skip_branch_with_pr: true
image: Visual Studio 2019
configuration: Release
clone_depth: 10
environment:
DOTNET_CHANNEL: 5.0
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_INSTALL_DIR: C:\Program Files\dotnet
DOTNET_NOLOGO: true
# DOTNET_SDK: 5.0.103
NET_CORE_VERSION: net5.0
NET_FRAMEWORK_VERSION: net48
NODE_JS_VERSION: lts
STEAM_TOKEN_DUMPER_NAME: ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
STEAM_TOKEN_DUMPER_TOKEN:
secure: uttQUE9ZK7BIa9SIbDkpUTMx7Slnl3zAPkRNzE465YgwxLdLEwv6yYR5QXCSZolb5Qq23Z/LmZNGd3M6B0+hbx3waWOeW2AiWvfCcnUmuT+3wfLJsgLbf1g4agFS7zsDgeRPfnNMzOxD8etelnA5YOOUMNB3RLw3fIdznNd+Fs6R0Ou3/1UavDuHKkbh1+A5
VARIANTS: generic generic-netf linux-arm linux-arm64 linux-x64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
matrix:
allow_failures:
- image: Visual Studio 2019 Preview
fast_finish: true
install:
- pwsh: >-
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
git submodule sync --recursive
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
git submodule update --init --recursive
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
if ($env:DOTNET_CHANNEL) {
dotnet --info
try {
&([scriptblock]::Create((Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1'))) -Channel "$env:DOTNET_CHANNEL" -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath
} catch [System.Net.WebException],[System.IO.IOException] {
# Not fatal for the remaining part of the script
Write-Host $_
}
}
if ($env:DOTNET_SDK) {
dotnet --info
try {
&([scriptblock]::Create((Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1'))) -Channel 'Current' -Version "$env:DOTNET_SDK" -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath
} catch [System.Net.WebException],[System.IO.IOException] {
# Not fatal for the remaining part of the script
Write-Host $_
}
}
- ps: Install-Product node "$env:NODE_JS_VERSION"
before_build:
- pwsh: >-
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
dotnet --info
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
node -v
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
npm -v
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
build_script:
- pwsh: >-
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
npm ci --no-progress --prefix ASF-ui
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
npm run-script deploy --no-progress --prefix ASF-ui
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
dotnet build -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
test_script:
- pwsh: >-
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
dotnet test ArchiSteamFarm.Tests -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
after_test:
- pwsh: >-
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
(Get-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs"
}
dotnet publish "$env:STEAM_TOKEN_DUMPER_NAME" -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" -o "out/$env:STEAM_TOKEN_DUMPER_NAME/$env:NET_CORE_VERSION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
dotnet publish "$env:STEAM_TOKEN_DUMPER_NAME" -c "$env:CONFIGURATION" -f "$env:NET_FRAMEWORK_VERSION" -o "out/$env:STEAM_TOKEN_DUMPER_NAME/$env:NET_FRAMEWORK_VERSION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
dotnet restore ArchiSteamFarm -p:ContinuousIntegrationBuild=true
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
$PublishBlock = {
param($variant)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
Set-Location "$env:APPVEYOR_BUILD_FOLDER"
if ($variant -like '*-netf') {
$targetFramework = $env:NET_FRAMEWORK_VERSION
} else {
$targetFramework = $env:NET_CORE_VERSION
}
if ($variant -like 'generic*') {
$variantArgs = '-p:TargetLatestRuntimePatch=false', '-p:UseAppHost=false'
} else {
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$variant"
}
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -f "$targetFramework" -o "out\$variant" "-p:ASFVariant=$variant" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
# If we're including any overlay for this variant, copy it to output directory
if (Test-Path "ArchiSteamFarm\overlay\$variant" -PathType Container) {
Copy-Item "ArchiSteamFarm\overlay\$variant\*" "out\$variant" -Recurse
}
# If we're including SteamTokenDumper plugin for this framework, copy it to output directory
if (Test-Path "out\$env:STEAM_TOKEN_DUMPER_NAME\$targetFramework" -PathType Container) {
if (!(Test-Path "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" -PathType Container)) {
New-Item -ItemType Directory -Path "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" > $null
}
Copy-Item "out\$env:STEAM_TOKEN_DUMPER_NAME\$targetFramework\*" "out\$variant\plugins\$env:STEAM_TOKEN_DUMPER_NAME" -Recurse
}
# Icon is available only in .NET Framework and .NET Core Windows build, we'll bundle the .ico file for other flavours
if (($targetFramework -eq "$env:NET_CORE_VERSION") -and !(Test-Path "out\$variant\ArchiSteamFarm.exe" -PathType Leaf)) {
Copy-Item 'resources\ASF.ico' "out\$variant\ArchiSteamFarm.ico"
}
# By default use fastest compression
$compressionArgs = '-mx=1'
# Include extra logic for builds marked for release
if ($env:APPVEYOR_REPO_TAG -eq 'true') {
# Update link in Changelog.html accordingly
if (Test-Path "out\$variant\Changelog.html" -PathType Leaf) {
(Get-Content "out\$variant\Changelog.html").Replace('ArchiSteamFarm/commits/main', "ArchiSteamFarm/releases/tag/$env:APPVEYOR_REPO_TAG_NAME") | Set-Content "out\$variant\Changelog.html"
}
}
7z a -bd -slp -tzip -mm=Deflate $compressionArgs "out\ASF-$variant.zip" "$env:APPVEYOR_BUILD_FOLDER\out\$variant\*"
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
# TODO: Change me to Push-AppveyorArtifact once https://github.com/appveyor/ci/issues/2183 is fixed
appveyor PushArtifact "out\ASF-$variant.zip" -FileName "ASF-$variant.zip" -DeploymentName "ASF-$variant.zip"
}
foreach ($variant in $env:VARIANTS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
Start-Job -Name "$variant" $PublishBlock -ArgumentList "$variant"
}
Get-Job | Receive-Job -Wait
deploy: off
notifications:
- provider: Webhook
url:
secure: i/og7KzkpbcWcKoUubrLH+KB6bkfqA55FHUlGxLepLmgZNQeNMiMoAFICOFY795fFrFfUNUKqwk7ApXE6HUyWMoiijLj7G/JBLTPkBiTCu8fZMTMqwQm6FiHB3+/0h0C+ukcrBEqnXYSQUh6znpKqJSTrIfXUQ7ftNuC966kBAw=
method: POST
body: >-
{
"avatar_url": "https://www.appveyor.com/assets/img/appveyor-logo-256.png",
"username": "AppVeyor",
"content": "[{{projectName}}:{{branch}}] {{commitMessage}} by {{commitAuthor}} ({{commitId}}) | **{{status}}** on {{buildUrl}}"
}
on_build_success: true
on_build_failure: true
on_build_status_changed: false

25
.github/renovate.json vendored
View File

@@ -1,25 +0,0 @@
{
"extends": [
"config:base",
":assignee(JustArchi)",
":automergeBranch",
":automergeDigest",
":automergeMinor",
":disableRateLimiting",
":label(🤖 Automatic)"
],
"git-submodules": {
"enabled": true
},
"packageRules": [
{
"allowedVersions": "<= 3.0",
"matchPackageNames": [ "Microsoft.Extensions.Configuration.Json", "Microsoft.Extensions.Logging.Configuration" ]
},
{
"allowedVersions": "<= 2.2.4",
"groupName": "MSTest packages",
"matchPackagePatterns": ["^MSTest\\..+"]
}
]
}

24
.github/renovate.json5 vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":assignee(JustArchi)",
":automergeBranch",
":automergeDigest",
":automergeMinor",
":disableDependencyDashboard",
":disableRateLimiting",
":label(🤖 Automatic)"
],
"git-submodules": {
"enabled": true
},
"packageRules": [
{
// TODO: <= 3.1 for Mono support, last failed version 6.12, https://steamcommunity.com/groups/archiasf/discussions/1/2997673517556002529
"allowedVersions": "<= 3.1",
"matchManagers": [ "nuget" ],
"matchPackageNames": [ "Microsoft.Extensions.Configuration.Json", "Microsoft.Extensions.Logging.Configuration" ]
}
]
}

View File

@@ -3,44 +3,44 @@ name: ASF-ci
on: [push, pull_request]
env:
CONFIGURATION: Release
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: 1
DOTNET_SDK_VERSION: 5.0.x
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
DOTNET_SDK_VERSION: 6.0.x
jobs:
main:
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v1.8.1
uses: actions/setup-dotnet@v1.9.0
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
- name: Verify .NET Core
run: dotnet --info
- name: Build ArchiSteamFarm and other projects
run: dotnet build -c "${{ env.CONFIGURATION }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
- name: Build ${{ matrix.configuration }} ArchiSteamFarm and other projects
run: dotnet build -c "${{ matrix.configuration }}" -p:ContinuousIntegrationBuild=true -p:UseAppHost=false --nologo
- name: Run ArchiSteamFarm.Tests
run: dotnet test ArchiSteamFarm.Tests -c "${{ env.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
- name: Upload latest strings for translation on Crowdin
continue-on-error: true
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && startsWith(matrix.os, 'ubuntu-') }}
uses: crowdin/github-action@1.2.0
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
uses: crowdin/github-action@1.4.6
with:
crowdin_branch_name: main
config: '.github/crowdin.yml'

View File

@@ -10,23 +10,26 @@ jobs:
strategy:
fail-fast: false
matrix:
configuration: [Debug, Release]
file: [Dockerfile, Dockerfile.Service]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.5.1
uses: docker/setup-buildx-action@v1.6.0
- name: Build Docker image from ${{ matrix.file }}
uses: docker/build-push-action@v2.6.1
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
uses: docker/build-push-action@v2.8.0
with:
context: .
file: ${{ matrix.file }}
platforms: ${{ env.PLATFORMS }}
build-args: STEAM_TOKEN_DUMPER_TOKEN=${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}
build-args: |
CONFIGURATION=${{ matrix.configuration }}
STEAM_TOKEN_DUMPER_TOKEN=${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}

View File

@@ -5,6 +5,7 @@ on:
types: [released]
env:
ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
PLATFORMS: linux/amd64,linux/arm,linux/arm64
TAG: latest
@@ -14,26 +15,35 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.5.1
uses: docker/setup-buildx-action@v1.6.0
- name: Login to ghcr.io
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Prepare private key for signing
shell: sh
run: |
set -eu
if [ -n "${ASF_PRIVATE_SNK-}" ]; then
echo "$ASF_PRIVATE_SNK" | base64 -d > "resources/ArchiSteamFarm.snk"
fi
- name: Prepare environment outputs
shell: sh
run: |
@@ -45,7 +55,7 @@ jobs:
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Build and publish Docker image from Dockerfile.Service
uses: docker/build-push-action@v2.6.1
uses: docker/build-push-action@v2.8.0
with:
context: .
file: Dockerfile.Service

View File

@@ -6,6 +6,7 @@ on:
- main
env:
ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
PLATFORMS: linux/amd64,linux/arm,linux/arm64
TAG: main
@@ -15,26 +16,35 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.5.1
uses: docker/setup-buildx-action@v1.6.0
- name: Login to ghcr.io
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Prepare private key for signing
shell: sh
run: |
set -eu
if [ -n "${ASF_PRIVATE_SNK-}" ]; then
echo "$ASF_PRIVATE_SNK" | base64 -d > "resources/ArchiSteamFarm.snk"
fi
- name: Prepare environment outputs
shell: sh
run: |
@@ -45,7 +55,7 @@ jobs:
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Build and publish Docker image from Dockerfile
uses: docker/build-push-action@v2.6.1
uses: docker/build-push-action@v2.8.0
with:
context: .
platforms: ${{ env.PLATFORMS }}

View File

@@ -6,6 +6,7 @@ on:
- '*'
env:
ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
PLATFORMS: linux/amd64,linux/arm,linux/arm64
TAG: released
@@ -15,26 +16,35 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.5.1
uses: docker/setup-buildx-action@v1.6.0
- name: Login to ghcr.io
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
uses: docker/login-action@v1.12.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Prepare private key for signing
shell: sh
run: |
set -eu
if [ -n "${ASF_PRIVATE_SNK-}" ]; then
echo "$ASF_PRIVATE_SNK" | base64 -d > "resources/ArchiSteamFarm.snk"
fi
- name: Prepare environment outputs
shell: sh
run: |
@@ -46,7 +56,7 @@ jobs:
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Build and publish Docker image from Dockerfile
uses: docker/build-push-action@v2.6.1
uses: docker/build-push-action@v2.8.0
with:
context: .
platforms: ${{ env.PLATFORMS }}

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Lock inactive threads
uses: dessant/lock-threads@v2.1.1
uses: dessant/lock-threads@v3.0.0
with:
issue-lock-inactive-days: 30
pr-lock-inactive-days: 30
issue-inactive-days: 60
pr-inactive-days: 60

View File

@@ -3,18 +3,19 @@ name: ASF-publish
on: [push, pull_request]
env:
ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
CONFIGURATION: Release
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: 1
DOTNET_SDK_VERSION: 5.0.x
NET_CORE_VERSION: net5.0
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_NOLOGO: true
DOTNET_SDK_VERSION: 6.0.x
NET_CORE_VERSION: net6.0
NET_FRAMEWORK_VERSION: net48
NODE_JS_VERSION: 'lts/*'
STEAM_TOKEN_DUMPER_NAME: ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
STEAM_TOKEN_DUMPER_TOKEN: ${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}
jobs:
main:
publish:
strategy:
fail-fast: false
matrix:
@@ -24,12 +25,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v1.8.1
uses: actions/setup-dotnet@v1.9.0
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
@@ -37,7 +38,7 @@ jobs:
run: dotnet --info
- name: Setup Node.js with npm
uses: actions/setup-node@v2.3.0
uses: actions/setup-node@v2.5.1
with:
check-latest: true
node-version: ${{ env.NODE_JS_VERSION }}
@@ -54,14 +55,44 @@ jobs:
- name: Publish ASF-ui
run: npm run-script deploy --no-progress --prefix ASF-ui
- name: Prepare private key for signing on Unix
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
shell: sh
run: |
set -eu
if [ -n "${ASF_PRIVATE_SNK-}" ]; then
echo "$ASF_PRIVATE_SNK" | base64 -d > "resources/ArchiSteamFarm.snk"
fi
- name: Prepare private key for signing on Windows
if: startsWith(matrix.os, 'windows-')
shell: pwsh
run: |
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
if ((Test-Path env:ASF_PRIVATE_SNK) -and ($env:ASF_PRIVATE_SNK)) {
echo "$env:ASF_PRIVATE_SNK" > "resources\ArchiSteamFarm.snk"
certutil -f -decode "resources\ArchiSteamFarm.snk" "resources\ArchiSteamFarm.snk"
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
}
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
shell: sh
run: |
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new"
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
fi
set -eu
if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" ]; then
sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs" > "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new"
mv "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs.new" "${STEAM_TOKEN_DUMPER_NAME}/SharedInfo.cs"
fi
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Windows
if: startsWith(matrix.os, 'windows-')
@@ -71,7 +102,7 @@ jobs:
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and ($env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs" -PathType Leaf)) {
(Get-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "$env:STEAM_TOKEN_DUMPER_NAME\SharedInfo.cs"
}
@@ -83,27 +114,33 @@ jobs:
run: dotnet publish "${{ env.STEAM_TOKEN_DUMPER_NAME }}" -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_FRAMEWORK_VERSION }}" -o "out/${{ env.STEAM_TOKEN_DUMPER_NAME }}/${{ env.NET_FRAMEWORK_VERSION }}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
- name: Restore packages in preparation for ArchiSteamFarm publishing
run: dotnet restore ArchiSteamFarm -p:ContinuousIntegrationBuild=true
run: dotnet restore ArchiSteamFarm -p:ContinuousIntegrationBuild=true --nologo
- name: Publish ArchiSteamFarm on Unix
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
env:
VARIANTS: generic linux-arm linux-arm64 linux-x64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
VARIANTS: generic linux-arm linux-arm64 linux-x64 osx-arm64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
shell: sh
run: |
set -eu
publish() {
if [ "$1" = 'generic' ]; then
local variantArgs="-p:TargetLatestRuntimePatch=false -p:UseAppHost=false"
variantArgs="-p:TargetLatestRuntimePatch=false -p:UseAppHost=false"
else
local variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $1"
variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $1 --self-contained"
fi
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${1}" "-p:ASFVariant=$1" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
# If we're including any overlay for this variant, copy it to output directory
if [ -d "ArchiSteamFarm/overlay/${1}" ]; then
variant_os="$(echo "$1" | cut -d '-' -f 1)"
if [ -d "ArchiSteamFarm/overlay/${variant_os}" ]; then
cp -pR "ArchiSteamFarm/overlay/${variant_os}/"* "out/${1}"
fi
if [ "$1" != "$variant_os" ] && [ -d "ArchiSteamFarm/overlay/${1}" ]; then
cp -pR "ArchiSteamFarm/overlay/${1}/"* "out/${1}"
fi
@@ -116,17 +153,57 @@ jobs:
# Include .ico file for all platforms, since only Windows script can bundle it inside the exe
cp "resources/ASF.ico" "out/${1}/ArchiSteamFarm.ico"
if command -v 7z >/dev/null; then
7z a -bd -slp -tzip -mm=Deflate -mx=1 "out/ASF-${1}.zip" "${GITHUB_WORKSPACE}/out/${1}/*"
elif command -v zip >/dev/null; then
(
cd "${GITHUB_WORKSPACE}/out/${1}"
zip -1 -q -r "../ASF-${1}.zip" .
)
else
echo "ERROR: No supported zip tool!"
return 1
fi
# By default use fastest compression
seven_zip_args="-mx=1"
zip_args="-1"
# Include extra logic for builds marked for release
case "$GITHUB_REF" in
"refs/tags/"*)
# Tweak compression args for release publishing
seven_zip_args="-mx=9 -mfb=258 -mpass=15"
zip_args="-9"
# Update link in Changelog.html accordingly
if [ -f "out/${1}/Changelog.html" ]; then
tag="$(echo "$GITHUB_REF" | cut -c 11-)"
sed "s/ArchiSteamFarm\/commits\/main/ArchiSteamFarm\/releases\/tag\/${tag}/g" "out/${1}/Changelog.html" > "out/${1}/Changelog.html.new"
mv "out/${1}/Changelog.html.new" "out/${1}/Changelog.html"
fi
;;
esac
# Create the final zip file
case "$(uname -s)" in
"Darwin")
# We prefer to use zip on OS X as 7z implementation on that OS doesn't handle file permissions (chmod +x)
if command -v zip >/dev/null; then
(
cd "${GITHUB_WORKSPACE}/out/${1}"
zip -q -r $zip_args "../ASF-${1}.zip" .
)
elif command -v 7z >/dev/null; then
7z a -bd -slp -tzip -mm=Deflate $seven_zip_args "out/ASF-${1}.zip" "${GITHUB_WORKSPACE}/out/${1}/*"
else
echo "ERROR: No supported zip tool!"
return 1
fi
;;
*)
if command -v 7z >/dev/null; then
7z a -bd -slp -tzip -mm=Deflate $seven_zip_args "out/ASF-${1}.zip" "${GITHUB_WORKSPACE}/out/${1}/*"
elif command -v zip >/dev/null; then
(
cd "${GITHUB_WORKSPACE}/out/${1}"
zip -q -r $zip_args "../ASF-${1}.zip" .
)
else
echo "ERROR: No supported zip tool!"
return 1
fi
;;
esac
}
jobs=""
@@ -143,7 +220,7 @@ jobs:
- name: Publish ArchiSteamFarm on Windows
if: startsWith(matrix.os, 'windows-')
env:
VARIANTS: generic generic-netf linux-arm linux-arm64 linux-x64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
VARIANTS: generic generic-netf linux-arm linux-arm64 linux-x64 osx-arm64 osx-x64 win-x64 # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
shell: pwsh
run: |
Set-StrictMode -Version Latest
@@ -168,7 +245,7 @@ jobs:
if ($variant -like 'generic*') {
$variantArgs = '-p:TargetLatestRuntimePatch=false', '-p:UseAppHost=false'
} else {
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$variant"
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$variant", '--self-contained'
}
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -f "$targetFramework" -o "out\$variant" "-p:ASFVariant=$variant" -p:ContinuousIntegrationBuild=true --no-restore --nologo $variantArgs
@@ -178,7 +255,13 @@ jobs:
}
# If we're including any overlay for this variant, copy it to output directory
if (Test-Path "ArchiSteamFarm\overlay\$variant" -PathType Container) {
$variant_os = $variant.Split('-', 2)[0];
if (Test-Path "ArchiSteamFarm\overlay\$variant_os" -PathType Container) {
Copy-Item "ArchiSteamFarm\overlay\$variant_os\*" "out\$variant" -Recurse
}
if (($variant -ne $variant_os) -and (Test-Path "ArchiSteamFarm\overlay\$variant" -PathType Container)) {
Copy-Item "ArchiSteamFarm\overlay\$variant\*" "out\$variant" -Recurse
}
@@ -201,33 +284,63 @@ jobs:
# Include extra logic for builds marked for release
if ($env:GITHUB_REF -like 'refs/tags/*') {
$tag = $env:GITHUB_REF.Substring(10)
# Tweak compression args for release publishing
$compressionArgs = '-mx=9', '-mfb=258', '-mpass=15'
# Update link in Changelog.html accordingly
if (Test-Path "out\$variant\Changelog.html" -PathType Leaf) {
$tag = $env:GITHUB_REF.Substring(10)
(Get-Content "out\$variant\Changelog.html").Replace('ArchiSteamFarm/commits/main', "ArchiSteamFarm/releases/tag/$tag") | Set-Content "out\$variant\Changelog.html"
}
}
# Create the final zip file
7z a -bd -slp -tzip -mm=Deflate $compressionArgs "out\ASF-$variant.zip" "$env:GITHUB_WORKSPACE\out\$variant\*"
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
# We can aid non-windows users by adding chmod +x flag to appropriate executables directly in the zip file
# This is ALMOST a hack, but works reliably enough
if (Test-Path "tools\zip_exec\zip_exec.exe" -PathType Leaf) {
$executableFiles = @()
if ($variant -like 'generic*') {
$executableFiles += 'ArchiSteamFarm.sh', 'ArchiSteamFarm-Service.sh'
} elseif (($variant -like 'linux*') -or ($variant -like 'osx*')) {
$executableFiles += 'ArchiSteamFarm', 'ArchiSteamFarm-Service.sh'
}
foreach ($executableFile in $executableFiles) {
tools\zip_exec\zip_exec.exe "out\ASF-$variant.zip" "$executableFile"
if ($LastExitCode -ne 0) {
throw "Last command failed."
}
}
}
}
foreach ($variant in $env:VARIANTS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
Start-Job -Name "$variant" $PublishBlock -ArgumentList "$variant"
# Limit active jobs in parallel to help with memory usage
$jobs = $(Get-Job -State Running)
while (@($jobs).Count -ge 5) {
Wait-Job -Job $jobs -Any | Out-Null
$jobs = $(Get-Job -State Running)
}
}
Get-Job | Receive-Job -Wait
- name: Upload ASF-generic
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-generic
path: out/ASF-generic.zip
@@ -235,52 +348,149 @@ jobs:
- name: Upload ASF-generic-netf
continue-on-error: true
if: startsWith(matrix.os, 'windows-')
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-generic-netf
path: out/ASF-generic-netf.zip
- name: Upload ASF-linux-arm
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-linux-arm
path: out/ASF-linux-arm.zip
- name: Upload ASF-linux-arm64
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-linux-arm64
path: out/ASF-linux-arm64.zip
- name: Upload ASF-linux-x64
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-linux-x64
path: out/ASF-linux-x64.zip
- name: Upload ASF-osx-arm64
continue-on-error: true
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-osx-arm64
path: out/ASF-osx-arm64.zip
- name: Upload ASF-osx-x64
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-osx-x64
path: out/ASF-osx-x64.zip
- name: Upload ASF-win-x64
continue-on-error: true
uses: actions/upload-artifact@v2.2.4
uses: actions/upload-artifact@v2.3.1
with:
name: ${{ matrix.os }}_ASF-win-x64
path: out/ASF-win-x64.zip
release:
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
needs: publish
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2.4.0
# TODO: It'd be perfect if we could match final artifacts to the platform they target, so e.g. linux build comes from the linux machine
# However, that is currently impossible due to https://github.com/dotnet/msbuild/issues/3897
# Therefore, we'll (sadly) pull artifacts from Windows machine only for now
- name: Download ASF-generic artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-generic
path: out
- name: Download ASF-generic-netf artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-generic-netf
path: out
- name: Download ASF-linux-arm artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-linux-arm
path: out
- name: Download ASF-linux-arm64 artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-linux-arm64
path: out
- name: Download ASF-linux-x64 artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-linux-x64
path: out
- name: Download ASF-osx-arm64 artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-osx-arm64
path: out
- name: Download ASF-osx-x64 artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-osx-x64
path: out
- name: Download ASF-win-x64 artifact from windows-latest
uses: actions/download-artifact@v2.1.0
with:
name: windows-latest_ASF-win-x64
path: out
- name: Import GPG key for signing
uses: crazy-max/ghaction-import-gpg@v4.1.0
with:
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
- name: Generate SHA-512 checksums and signature
shell: sh
run: |
set -eu
(
cd "out"
sha512sum *.zip > SHA512SUMS
gpg -a -b -o SHA512SUMS.sign SHA512SUMS
)
- name: Upload SHA512SUMS
continue-on-error: true
uses: actions/upload-artifact@v2.3.1
with:
name: SHA512SUMS
path: out/SHA512SUMS
- name: Upload SHA512SUMS.sign
continue-on-error: true
uses: actions/upload-artifact@v2.3.1
with:
name: SHA512SUMS.sign
path: out/SHA512SUMS.sign
- name: Create ArchiSteamFarm GitHub release
id: github_release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/create-release@v1.1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ArchiSteamFarm V${{ github.ref }}
@@ -288,7 +498,6 @@ jobs:
prerelease: true
- name: Upload ASF-generic to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -299,7 +508,6 @@ jobs:
asset_content_type: application/zip
- name: Upload ASF-generic-netf to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -310,7 +518,6 @@ jobs:
asset_content_type: application/zip
- name: Upload ASF-linux-arm to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -321,7 +528,6 @@ jobs:
asset_content_type: application/zip
- name: Upload ASF-linux-arm64 to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -332,7 +538,6 @@ jobs:
asset_content_type: application/zip
- name: Upload ASF-linux-x64 to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -342,8 +547,17 @@ jobs:
asset_name: ASF-linux-x64.zip
asset_content_type: application/zip
- name: Upload ASF-osx-arm64 to GitHub release
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.github_release.outputs.upload_url }}
asset_path: out/ASF-osx-arm64.zip
asset_name: ASF-osx-arm64.zip
asset_content_type: application/zip
- name: Upload ASF-osx-x64 to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -354,7 +568,6 @@ jobs:
asset_content_type: application/zip
- name: Upload ASF-win-x64 to GitHub release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows-') }}
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -363,3 +576,23 @@ jobs:
asset_path: out/ASF-win-x64.zip
asset_name: ASF-win-x64.zip
asset_content_type: application/zip
- name: Upload SHA512SUMS to GitHub release
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.github_release.outputs.upload_url }}
asset_path: out/SHA512SUMS
asset_name: SHA512SUMS
asset_content_type: text/plain
- name: Upload SHA512SUMS.sign to GitHub release
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.github_release.outputs.upload_url }}
asset_path: out/SHA512SUMS.sign
asset_name: SHA512SUMS.sign
asset_content_type: text/plain

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.4.0
with:
submodules: recursive
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
@@ -26,7 +26,7 @@ jobs:
git reset --hard origin/master
- name: Download latest translations from Crowdin
uses: crowdin/github-action@1.2.0
uses: crowdin/github-action@1.4.6
with:
upload_sources: false
download_translations: true
@@ -37,13 +37,13 @@ jobs:
project_id: ${{ secrets.ASF_CROWDIN_PROJECT_ID }}
token: ${{ secrets.ASF_CROWDIN_API_TOKEN }}
- name: Import GPG key for wiki
uses: crazy-max/ghaction-import-gpg@v3.1.0
- name: Import GPG key for signing
uses: crazy-max/ghaction-import-gpg@v4.1.0
with:
gpg-private-key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
git-user-signingkey: true
git-commit-gpgsign: true
workdir: wiki
gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
git_config_global: true
git_user_signingkey: true
git_commit_gpgsign: true
- name: Commit the changes to wiki
shell: sh
@@ -66,13 +66,6 @@ jobs:
directory: wiki
repository: ${{ github.repository }}.wiki
- name: Import GPG key for ASF
uses: crazy-max/ghaction-import-gpg@v3.1.0
with:
gpg-private-key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}
git-user-signingkey: true
git-commit-gpgsign: true
- name: Commit the changes to ASF
shell: sh
run: |

8
.gitignore vendored
View File

@@ -7,6 +7,9 @@
# Ignore all files in custom in-tree config directory (if exists)
ArchiSteamFarm/config
# Ignore private SNK key (if exists)
resources/ArchiSteamFarm.snk
# Ignore local log + debug of development builds
ArchiSteamFarm/log.txt
ArchiSteamFarm/debug
@@ -22,6 +25,7 @@ ArchiSteamFarm/logs
# |_____||_||_| |_| \__,_|/_/\_\
#
# https://github.com/github/gitignore/blob/master/Global/Linux.gitignore
# 4f7062e132d7f88e68ab737e64fef872bd3a491f
*~
@@ -44,6 +48,7 @@ ArchiSteamFarm/logs
# |_| |_| |_| \__,_| \___| \___/ |____/
#
# https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# 2bb963b16a1957c865335e53537036c2e97399b5
# General
.DS_Store
@@ -80,6 +85,7 @@ Temporary Items
# |_|
#
# https://github.com/github/gitignore/blob/master/Global/MonoDevelop.gitignore
# e8b2e1a9cc7c9ca49bb05c20a4c4491b85feba6d
#User Specific
*.userprefs
@@ -97,6 +103,7 @@ test-results/
# \_/ |_||___/ \__,_| \__,_||_||____/ \__| \__,_| \__,_||_| \___/
#
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# 888439ee893d0097862f1d510585bd0e3cfd500f
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -494,6 +501,7 @@ FodyWeavers.xsd
# \_/\_/ |_||_| |_| \__,_| \___/ \_/\_/ |___/
#
# https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# 5808b77453dec299d4daf8557b05a80be832a5b8
# Windows thumbnail cache files
Thumbs.db

2
ASF-ui

Submodule ASF-ui updated: cf95b19063...156992e88d

View File

@@ -5,12 +5,14 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="JustArchiNET.Madness" IncludeAssets="compile" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" IncludeAssets="compile" />
</ItemGroup>

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,42 +26,40 @@ using ArchiSteamFarm.Web;
using ArchiSteamFarm.Web.Responses;
using Newtonsoft.Json;
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// This is example class that shows how you can call third-party services within your plugin
// You've always wanted from your ASF to post cats, right? Now is your chance!
// P.S. The code is almost 1:1 copy from the one I use in ArchiBot, you can thank me later
internal static class CatAPI {
private const string URL = "https://aws.random.cat";
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
internal static async Task<string?> GetRandomCatURL(WebBrowser webBrowser) {
if (webBrowser == null) {
throw new ArgumentNullException(nameof(webBrowser));
}
// This is example class that shows how you can call third-party services within your plugin
// You've always wanted from your ASF to post cats, right? Now is your chance!
// P.S. The code is almost 1:1 copy from the one I use in ArchiBot, you can thank me later
internal static class CatAPI {
private const string URL = "https://aws.random.cat";
Uri request = new(URL + "/meow");
internal static async Task<string?> GetRandomCatURL(WebBrowser webBrowser) {
ArgumentNullException.ThrowIfNull(webBrowser);
ObjectResponse<MeowResponse>? response = await webBrowser.UrlGetToJsonObject<MeowResponse>(request).ConfigureAwait(false);
Uri request = new($"{URL}/meow");
if (response == null) {
return null;
}
ObjectResponse<MeowResponse>? response = await webBrowser.UrlGetToJsonObject<MeowResponse>(request).ConfigureAwait(false);
if (string.IsNullOrEmpty(response.Content.Link)) {
throw new InvalidOperationException(nameof(response.Content.Link));
}
return Uri.EscapeUriString(response.Content.Link);
if (response == null) {
return null;
}
if (string.IsNullOrEmpty(response.Content.Link)) {
throw new InvalidOperationException(nameof(response.Content.Link));
}
return Uri.EscapeDataString(response.Content.Link);
}
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
private sealed class MeowResponse {
[JsonProperty(PropertyName = "file", Required = Required.Always)]
internal readonly string Link = "";
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
private sealed class MeowResponse {
[JsonProperty(PropertyName = "file", Required = Required.Always)]
internal readonly string Link = "";
[JsonConstructor]
private MeowResponse() { }
}
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
[JsonConstructor]
private MeowResponse() { }
}
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,26 +27,26 @@ using ArchiSteamFarm.IPC.Controllers.Api;
using ArchiSteamFarm.IPC.Responses;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// This is an example class which shows you how you can extend ASF's API with your own custom API routes and controllers
// You're free to decide whether you want to integrate with existing ASF concepts (such as ArchiController/GenericResponse), or roll out your own
// All API controllers will be discovered during our Kestrel initialization using attributes mapping, you're also getting usual ASF goodies such as swagger documentation out of the box
[Route("/Api/Cat")]
public sealed class CatController : ArchiController {
/// <summary>
/// Fetches URL of a random cat picture.
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> CatGet() {
if (ASF.WebBrowser == null) {
throw new InvalidOperationException(nameof(ASF.WebBrowser));
}
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
string? link = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
return !string.IsNullOrEmpty(link) ? Ok(new GenericResponse<string>(link)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
// This is an example class which shows you how you can extend ASF's API with your own custom API routes and controllers
// You're free to decide whether you want to integrate with existing ASF concepts (such as ArchiController/GenericResponse), or roll out your own
// All API controllers will be discovered during our Kestrel initialization using attributes mapping, you're also getting usual ASF goodies such as swagger documentation out of the box
[Route("/Api/Cat")]
public sealed class CatController : ArchiController {
/// <summary>
/// Fetches URL of a random cat picture.
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> CatGet() {
if (ASF.WebBrowser == null) {
throw new InvalidOperationException(nameof(ASF.WebBrowser));
}
string? link = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
return !string.IsNullOrEmpty(link) ? Ok(new GenericResponse<string>(link)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if NETFRAMEWORK
using ArchiSteamFarm.Compatibility;
#endif
using System;
using System.Collections.Generic;
using System.Composition;
@@ -31,154 +28,160 @@ using ArchiSteamFarm.Core;
using ArchiSteamFarm.Plugins.Interfaces;
using ArchiSteamFarm.Steam;
using ArchiSteamFarm.Steam.Data;
using ArchiSteamFarm.Steam.Storage;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamKit2;
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// In order for your plugin to work, it must export generic ASF's IPlugin interface
[Export(typeof(IPlugin))]
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
// Your plugin class should inherit the plugin interfaces it wants to handle
// If you do not want to handle a particular action (e.g. OnBotMessage that is offered in IBotMessage), it's the best idea to not inherit it at all
// This will keep your code compact, efficient and less dependent. You can always add additional interfaces when you'll need them, this example project will inherit quite a bit of them to show you potential usage
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
public string Name => nameof(ExamplePlugin);
// In order for your plugin to work, it must export generic ASF's IPlugin interface
[Export(typeof(IPlugin))]
// This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
// Your plugin class should inherit the plugin interfaces it wants to handle
// If you do not want to handle a particular action (e.g. OnBotMessage that is offered in IBotMessage), it's the best idea to not inherit it at all
// This will keep your code compact, efficient and less dependent. You can always add additional interfaces when you'll need them, this example project will inherit quite a bit of them to show you potential usage
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
public string Name => nameof(ExamplePlugin);
// Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public)
[JsonProperty]
public bool CustomIsEnabledField { get; private set; } = true;
// This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
// This method, apart from being called before any bot initialization takes place, allows you to read custom global config properties that are not recognized by ASF
// Thanks to that, you can extend default ASF config with your own stuff, then parse it here in order to customize your plugin during runtime
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
// In addition to that, this method also guarantees that all plugins were already OnLoaded(), which allows cross-plugins-communication to be possible
public void OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (additionalConfigProperties == null) {
return;
}
// Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public)
[JsonProperty]
public bool CustomIsEnabledField { get; private set; } = true;
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
// It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future
switch (configProperty) {
case nameof(ExamplePlugin) + "TestProperty" when configValue.Type == JTokenType.Boolean:
bool exampleBooleanValue = configValue.Value<bool>();
ASF.ArchiLogger.LogGenericInfo(nameof(ExamplePlugin) + "TestProperty boolean property has been found with a value of: " + exampleBooleanValue);
// This method, apart from being called before any bot initialization takes place, allows you to read custom global config properties that are not recognized by ASF
// Thanks to that, you can extend default ASF config with your own stuff, then parse it here in order to customize your plugin during runtime
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
// In addition to that, this method also guarantees that all plugins were already OnLoaded(), which allows cross-plugins-communication to be possible
public Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (additionalConfigProperties == null) {
return Task.CompletedTask;
}
break;
}
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
// It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future
switch (configProperty) {
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.Type == JTokenType.Boolean:
bool exampleBooleanValue = configValue.Value<bool>();
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of: {exampleBooleanValue}");
break;
}
}
// This method is called when unknown command is received (starting with CommandPrefix)
// This allows you to recognize the command yourself and implement custom commands
// Keep in mind that there is no guarantee what is the actual access of steamID, so you should do the appropriate access checking yourself
// You can use either ASF's default functions for that, or implement your own logic as you please
// Since ASF already had to do initial parsing in order to determine that the command is unknown, args[] are splitted using standard ASF delimiters
// If by any chance you want to handle message in its raw format, you also have it available, although for usual ASF pattern you can most likely stick with args[] exclusively. The message has CommandPrefix already stripped for your convenience
// If you do not recognize the command, just return null/empty and allow ASF to gracefully return "unknown command" to user on usual basis
public async Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
// In comparison with OnBotMessage(), we're using asynchronous CatAPI call here, so we declare our method as async and return the message as usual
// Notice how we handle access here as well, it'll work only for FamilySharing+
switch (args[0].ToUpperInvariant()) {
case "CAT" when bot.HasAccess(steamID, BotConfig.EAccess.FamilySharing):
// Notice how we can decide whether to use bot's AWH WebBrowser or ASF's one. For Steam-related requests, AWH's one should always be used, for third-party requests like those it doesn't really matter
// Still, it makes sense to pass AWH's one, so in case you get some errors or alike, you know from which bot instance they come from. It's similar to using Bot's ArchiLogger compared to ASF's one
string? randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
// ASF interface methods usually expect a Task as a return value, this allows you to optionally implement async operations in your functions (with async Task function signature)
// If your method does not implement any async operations (is fully synchronous), you could in theory still mark it as async, but a better idea is to just return Task.CompletedTask from it, like here
return Task.CompletedTask;
}
return !string.IsNullOrEmpty(randomCatURL) ? randomCatURL : "God damn it, we're out of cats, care to notify my master? Thanks!";
default:
return null;
}
}
// This method is called when unknown command is received (starting with CommandPrefix)
// This allows you to recognize the command yourself and implement custom commands
// Keep in mind that there is no guarantee what is the actual access of steamID, so you should do the appropriate access checking yourself
// You can use either ASF's default functions for that, or implement your own logic as you please
// Since ASF already had to do initial parsing in order to determine that the command is unknown, args[] are splitted using standard ASF delimiters
// If by any chance you want to handle message in its raw format, you also have it available, although for usual ASF pattern you can most likely stick with args[] exclusively. The message has CommandPrefix already stripped for your convenience
// If you do not recognize the command, just return null/empty and allow ASF to gracefully return "unknown command" to user on usual basis
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
// In comparison with OnBotMessage(), we're using asynchronous CatAPI call here, so we declare our method as async and return the message as usual
// Notice how we handle access here as well, it'll work only for FamilySharing+
switch (args[0].ToUpperInvariant()) {
case "CAT" when access >= EAccess.FamilySharing:
// Notice how we can decide whether to use bot's AWH WebBrowser or ASF's one. For Steam-related requests, AWH's one should always be used, for third-party requests like those it doesn't really matter
// Still, it makes sense to pass AWH's one, so in case you get some errors or alike, you know from which bot instance they come from. It's similar to using Bot's ArchiLogger compared to ASF's one
string? randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
// This method is called when bot is destroyed, e.g. on config removal
// You should ensure that all of your references to this bot instance are cleared - most of the time this is anything you created in OnBotInit(), including deep roots in your custom modules
// This doesn't have to be done immediately (e.g. no need to cancel existing work), but it should be done in timely manner when everything is finished
// Doing so will allow the garbage collector to dispose the bot afterwards, refraining from doing so will create a "memory leak" by keeping the reference alive
public void OnBotDestroy(Bot bot) { }
// This method is called when bot is disconnected from Steam network, you may want to use this info in some kind of way, or not
// ASF tries its best to provide logical reason why the disconnection has happened, and will use EResult.OK if the disconnection was initiated by us (e.g. as part of a command)
// Still, you should take anything other than EResult.OK with a grain of salt, unless you want to assume that Steam knows why it disconnected us (hehe, you bet)
public void OnBotDisconnected(Bot bot, EResult reason) { }
// This method is called when bot receives a friend request or group invite that ASF isn't willing to accept
// It allows you to generate a response whether ASF should accept it (true) or proceed like usual (false)
// If you wanted to do extra filtering (e.g. friend requests only), you can interpret the steamID as SteamID (SteamKit2 type) and then operate on AccountType
// As an example, we'll run a trade bot that is open to all friend/group invites, therefore we'll accept all of them here
public Task<bool> OnBotFriendRequest(Bot bot, ulong steamID) => Task.FromResult(true);
// This method is called at the end of Bot's constructor
// You can initialize all your per-bot structures here
// In general you should do that only when you have a particular need of custom modules or alike, since ASF's plugin system will always provide bot to you as a function argument
public void OnBotInit(Bot bot) {
// Apart of those two that are already provided by ASF, you can also initialize your own logger with your plugin's name, if needed
bot.ArchiLogger.LogGenericInfo("Our bot named " + bot.BotName + " has been initialized, and we're letting you know about it from our " + nameof(ExamplePlugin) + "!");
ASF.ArchiLogger.LogGenericWarning("In case we won't have a bot reference or have something process-wide to log, we can also use ASF's logger!");
}
// This method, apart from being called during bot modules initialization, allows you to read custom bot config properties that are not recognized by ASF
// Thanks to that, you can extend default bot config with your own stuff, then parse it here in order to customize your plugin during runtime
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
// Also keep in mind that this function can be called multiple times, e.g. when user edits his bot configs during runtime
// Take a look at OnASFInit() for example parsing code
public async void OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
// ASF marked this message as synchronous, in case we have async code to execute, we can just use async void return
// For example, we'll ensure that every bot starts paused regardless of Paused property, in order to do this, we'll just call Pause here in InitModules()
// Thanks to the fact that this method is called with each bot config reload, we'll ensure that our bot stays paused even if it'd get unpaused otherwise
bot.ArchiLogger.LogGenericInfo("Pausing this bot as asked from the plugin");
await bot.Actions.Pause(true).ConfigureAwait(false);
}
// This method is called when the bot is successfully connected to Steam network and it's a good place to schedule any on-connected tasks, as AWH is also expected to be available shortly
public void OnBotLoggedOn(Bot bot) { }
// This method is called when bot receives a message that is NOT a command (in other words, a message that doesn't start with CommandPrefix)
// Normally ASF entirely ignores such messages as the program should not respond to something that isn't recognized
// Therefore this function allows you to catch all such messages and handle them yourself
// Keep in mind that there is no guarantee what is the actual access of steamID, so you should do the appropriate access checking yourself
// You can use either ASF's default functions for that, or implement your own logic as you please
// If you do not intend to return any response to user, just return null/empty and ASF will proceed with the silence as usual
public Task<string?> OnBotMessage(Bot bot, ulong steamID, string message) {
// Normally ASF will expect from you async-capable responses, such as Task<string>. This allows you to make your code fully asynchronous which is a core foundation on which ASF is built upon
// Since in this method we're not doing any async stuff, instead of defining this method as async (pointless), we just need to wrap our responses in Task.FromResult<>()
if (Bot.BotsReadOnly == null) {
throw new InvalidOperationException(nameof(Bot.BotsReadOnly));
}
// As a starter, we can for example ignore messages sent from our own bots, since otherwise they can run into a possible infinite loop of answering themselves
if (Bot.BotsReadOnly.Values.Any(existingBot => existingBot.SteamID == steamID)) {
return Task.FromResult<string?>(null);
}
// If this message doesn't come from one of our bots, we can reply to the user in some pre-defined way
bot.ArchiLogger.LogGenericTrace("Hey boss, we got some unknown message here!");
return Task.FromResult((string?) "I didn't get that, did you mean to use a command?");
}
// This method is called when bot receives a trade offer that ASF isn't willing to accept (ignored and rejected trades)
// It allows you not only to analyze such trades, but generate a response whether ASF should accept it (true), or proceed like usual (false)
// Thanks to that, you can implement custom rules for all trades that aren't handled by ASF, for example cross-set trading on your own custom rules
// You'd implement your own logic here, as an example we'll allow all trades to be accepted if the bot's name starts from "TrashBot"
public Task<bool> OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) => Task.FromResult(bot.BotName.StartsWith("TrashBot", StringComparison.OrdinalIgnoreCase));
// This is the earliest method that will be called, right after loading the plugin, long before any bot initialization takes place
// It's a good place to initialize all potential (non-bot-specific) structures that you will need across lifetime of your plugin, such as global timers, concurrent dictionaries and alike
// If you do not have any global structures to initialize, you can leave this function empty
// At this point you can access core ASF's functionality, such as logging, but more advanced structures (like ASF's WebBrowser) will be available in OnASFInit(), which itself takes place after every plugin gets OnLoaded()
// Typically you should use this function only for preparing core structures of your plugin, and optionally also sending a message to the user (e.g. support link, welcome message or similar), ASF-specific things should usually happen in OnASFInit()
public void OnLoaded() {
ASF.ArchiLogger.LogGenericInfo("Hey! Thanks for checking if our example plugin works fine, this is a confirmation that indeed " + nameof(OnLoaded) + "() method was called!");
ASF.ArchiLogger.LogGenericInfo("Good luck in whatever you're doing!");
return !string.IsNullOrEmpty(randomCatURL) ? randomCatURL : "God damn it, we're out of cats, care to notify my master? Thanks!";
default:
return null;
}
}
// This method is called when bot is destroyed, e.g. on config removal
// You should ensure that all of your references to this bot instance are cleared - most of the time this is anything you created in OnBotInit(), including deep roots in your custom modules
// This doesn't have to be done immediately (e.g. no need to cancel existing work), but it should be done in timely manner when everything is finished
// Doing so will allow the garbage collector to dispose the bot afterwards, refraining from doing so will create a "memory leak" by keeping the reference alive
public Task OnBotDestroy(Bot bot) => Task.CompletedTask;
// This method is called when bot is disconnected from Steam network, you may want to use this info in some kind of way, or not
// ASF tries its best to provide logical reason why the disconnection has happened, and will use EResult.OK if the disconnection was initiated by us (e.g. as part of a command)
// Still, you should take anything other than EResult.OK with a grain of salt, unless you want to assume that Steam knows why it disconnected us (hehe, you bet)
public Task OnBotDisconnected(Bot bot, EResult reason) => Task.CompletedTask;
// This method is called when bot receives a friend request or group invite that ASF isn't willing to accept
// It allows you to generate a response whether ASF should accept it (true) or proceed like usual (false)
// If you wanted to do extra filtering (e.g. friend requests only), you can interpret the steamID as SteamID (SteamKit2 type) and then operate on AccountType
// As an example, we'll run a trade bot that is open to all friend/group invites, therefore we'll accept all of them here
public Task<bool> OnBotFriendRequest(Bot bot, ulong steamID) => Task.FromResult(true);
// This method is called at the end of Bot's constructor
// You can initialize all your per-bot structures here
// In general you should do that only when you have a particular need of custom modules or alike, since ASF's plugin system will always provide bot to you as a function argument
public Task OnBotInit(Bot bot) {
// Apart of those two that are already provided by ASF, you can also initialize your own logger with your plugin's name, if needed
bot.ArchiLogger.LogGenericInfo($"Our bot named {bot.BotName} has been initialized, and we're letting you know about it from our {nameof(ExamplePlugin)}!");
ASF.ArchiLogger.LogGenericWarning("In case we won't have a bot reference or have something process-wide to log, we can also use ASF's logger!");
return Task.CompletedTask;
}
// This method, apart from being called during bot modules initialization, allows you to read custom bot config properties that are not recognized by ASF
// Thanks to that, you can extend default bot config with your own stuff, then parse it here in order to customize your plugin during runtime
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
// Also keep in mind that this function can be called multiple times, e.g. when user edits his bot configs during runtime
// Take a look at OnASFInit() for example parsing code
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
// For example, we'll ensure that every bot starts paused regardless of Paused property, in order to do this, we'll just call Pause here in InitModules()
// Thanks to the fact that this method is called with each bot config reload, we'll ensure that our bot stays paused even if it'd get unpaused otherwise
bot.ArchiLogger.LogGenericInfo("Pausing this bot as asked from the plugin");
await bot.Actions.Pause(true).ConfigureAwait(false);
}
// This method is called when the bot is successfully connected to Steam network and it's a good place to schedule any on-connected tasks, as AWH is also expected to be available shortly
public Task OnBotLoggedOn(Bot bot) => Task.CompletedTask;
// This method is called when bot receives a message that is NOT a command (in other words, a message that doesn't start with CommandPrefix)
// Normally ASF entirely ignores such messages as the program should not respond to something that isn't recognized
// Therefore this function allows you to catch all such messages and handle them yourself
// Keep in mind that there is no guarantee what is the actual access of steamID, so you should do the appropriate access checking yourself
// You can use either ASF's default functions for that, or implement your own logic as you please
// If you do not intend to return any response to user, just return null/empty and ASF will proceed with the silence as usual
public Task<string?> OnBotMessage(Bot bot, ulong steamID, string message) {
// Normally ASF will expect from you async-capable responses, such as Task<string>. This allows you to make your code fully asynchronous which is a core foundation on which ASF is built upon
// Since in this method we're not doing any async stuff, instead of defining this method as async (pointless), we just need to wrap our responses in Task.FromResult<>()
if (Bot.BotsReadOnly == null) {
throw new InvalidOperationException(nameof(Bot.BotsReadOnly));
}
// As a starter, we can for example ignore messages sent from our own bots, since otherwise they can run into a possible infinite loop of answering themselves
if (Bot.BotsReadOnly.Values.Any(existingBot => existingBot.SteamID == steamID)) {
return Task.FromResult<string?>(null);
}
// If this message doesn't come from one of our bots, we can reply to the user in some pre-defined way
bot.ArchiLogger.LogGenericTrace("Hey boss, we got some unknown message here!");
return Task.FromResult((string?) "I didn't get that, did you mean to use a command?");
}
// This method is called when bot receives a trade offer that ASF isn't willing to accept (ignored and rejected trades)
// It allows you not only to analyze such trades, but generate a response whether ASF should accept it (true), or proceed like usual (false)
// Thanks to that, you can implement custom rules for all trades that aren't handled by ASF, for example cross-set trading on your own custom rules
// You'd implement your own logic here, as an example we'll allow all trades to be accepted if the bot's name starts from "TrashBot"
public Task<bool> OnBotTradeOffer(Bot bot, TradeOffer tradeOffer) => Task.FromResult(bot.BotName.StartsWith("TrashBot", StringComparison.OrdinalIgnoreCase));
// This is the earliest method that will be called, right after loading the plugin, long before any bot initialization takes place
// It's a good place to initialize all potential (non-bot-specific) structures that you will need across lifetime of your plugin, such as global timers, concurrent dictionaries and alike
// If you do not have any global structures to initialize, you can leave this function empty
// At this point you can access core ASF's functionality, such as logging, but more advanced structures (like ASF's WebBrowser) will be available in OnASFInit(), which itself takes place after every plugin gets OnLoaded()
// Typically you should use this function only for preparing core structures of your plugin, and optionally also sending a message to the user (e.g. support link, welcome message or similar), ASF-specific things should usually happen in OnASFInit()
public Task OnLoaded() {
ASF.ArchiLogger.LogGenericInfo($"Hey! Thanks for checking if our example plugin works fine, this is a confirmation that indeed {nameof(OnLoaded)}() method was called!");
ASF.ArchiLogger.LogGenericInfo("Good luck in whatever you're doing!");
return Task.CompletedTask;
}
}

View File

@@ -5,6 +5,7 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
</ItemGroup>

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,41 +24,44 @@ using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Plugins.Interfaces;
namespace ArchiSteamFarm.CustomPlugins.PeriodicGC {
[Export(typeof(IPlugin))]
[SuppressMessage("ReSharper", "UnusedType.Global")]
internal sealed class PeriodicGCPlugin : IPlugin {
private const byte GCPeriod = 60; // In seconds
namespace ArchiSteamFarm.CustomPlugins.PeriodicGC;
private static readonly object LockObject = new();
private static readonly Timer PeriodicGCTimer = new(PerformGC);
[Export(typeof(IPlugin))]
[SuppressMessage("ReSharper", "UnusedType.Global")]
internal sealed class PeriodicGCPlugin : IPlugin {
private const byte GCPeriod = 60; // In seconds
public string Name => nameof(PeriodicGCPlugin);
private static readonly object LockObject = new();
private static readonly Timer PeriodicGCTimer = new(PerformGC);
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
public string Name => nameof(PeriodicGCPlugin);
public void OnLoaded() {
TimeSpan timeSpan = TimeSpan.FromSeconds(GCPeriod);
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
ASF.ArchiLogger.LogGenericWarning("Periodic GC will occur every " + timeSpan.ToHumanReadable() + ". Please keep in mind that this plugin should be used for debugging tests only.");
public Task OnLoaded() {
TimeSpan timeSpan = TimeSpan.FromSeconds(GCPeriod);
lock (LockObject) {
PeriodicGCTimer.Change(timeSpan, timeSpan);
}
ASF.ArchiLogger.LogGenericWarning($"Periodic GC will occur every {timeSpan.ToHumanReadable()}. Please keep in mind that this plugin should be used for debugging tests only.");
lock (LockObject) {
PeriodicGCTimer.Change(timeSpan, timeSpan);
}
private static void PerformGC(object? state) {
ASF.ArchiLogger.LogGenericWarning("Performing GC, current memory: " + (GC.GetTotalMemory(false) / 1024) + " KB.");
return Task.CompletedTask;
}
lock (LockObject) {
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
}
private static void PerformGC(object? state = null) {
ASF.ArchiLogger.LogGenericWarning($"Performing GC, current memory: {GC.GetTotalMemory(false) / 1024} KB.");
ASF.ArchiLogger.LogGenericWarning("GC finished, current memory: " + (GC.GetTotalMemory(false) / 1024) + " KB.");
lock (LockObject) {
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
}
ASF.ArchiLogger.LogGenericWarning($"GC finished, current memory: {GC.GetTotalMemory(false) / 1024} KB.");
}
}

View File

@@ -5,6 +5,7 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
@@ -12,7 +13,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="System.Collections.Immutable" IncludeAssets="compile" />
<PackageReference Include="JustArchiNET.Madness" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,270 +19,288 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if NETFRAMEWORK
using ArchiSteamFarm.Compatibility;
using File = System.IO.File;
using Path = System.IO.Path;
#else
using System.IO;
#endif
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal sealed class GlobalCache : SerializableFile {
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, nameof(SteamTokenDumper) + ".cache");
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, uint> AppChangeNumbers = new();
internal sealed class GlobalCache : SerializableFile {
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, $"{nameof(SteamTokenDumper)}.cache");
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> AppTokens = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, uint> AppChangeNumbers = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, string> DepotKeys = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> AppTokens = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> PackageTokens = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, string> DepotKeys = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> SubmittedApps = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> PackageTokens = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, string> SubmittedDepots = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> SubmittedApps = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> SubmittedPackages = new();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, string> SubmittedDepots = new();
[JsonProperty(Required = Required.DisallowNull)]
internal uint LastChangeNumber { get; private set; }
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, ulong> SubmittedPackages = new();
internal GlobalCache() => FilePath = SharedFilePath;
[JsonProperty(Required = Required.DisallowNull)]
internal uint LastChangeNumber { get; private set; }
internal ulong GetAppToken(uint appID) => AppTokens[appID];
internal GlobalCache() => FilePath = SharedFilePath;
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) == false) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(appToken => appToken.Key, appToken => appToken.Value);
internal Dictionary<uint, string> GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) == false) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(depotKey => depotKey.Key, depotKey => depotKey.Value);
internal Dictionary<uint, ulong> GetPackageTokensForSubmission() => PackageTokens.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) == false) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(packageToken => packageToken.Key, packageToken => packageToken.Value);
[UsedImplicitly]
public bool ShouldSerializeAppChangeNumbers() => !AppChangeNumbers.IsEmpty;
internal static async Task<GlobalCache?> Load() {
if (!File.Exists(SharedFilePath)) {
GlobalCache result = new();
[UsedImplicitly]
public bool ShouldSerializeAppTokens() => !AppTokens.IsEmpty;
Utilities.InBackground(result.Save);
[UsedImplicitly]
public bool ShouldSerializeDepotKeys() => !DepotKeys.IsEmpty;
return result;
}
[UsedImplicitly]
public bool ShouldSerializeLastChangeNumber() => LastChangeNumber > 0;
GlobalCache? globalCache;
[UsedImplicitly]
public bool ShouldSerializePackageTokens() => !PackageTokens.IsEmpty;
try {
string json = await Compatibility.File.ReadAllTextAsync(SharedFilePath).ConfigureAwait(false);
[UsedImplicitly]
public bool ShouldSerializeSubmittedApps() => !SubmittedApps.IsEmpty;
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
[UsedImplicitly]
public bool ShouldSerializeSubmittedDepots() => !SubmittedDepots.IsEmpty;
return null;
}
[UsedImplicitly]
public bool ShouldSerializeSubmittedPackages() => !SubmittedPackages.IsEmpty;
globalCache = JsonConvert.DeserializeObject<GlobalCache>(json);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
internal ulong GetAppToken(uint appID) => AppTokens[appID];
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) == false) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value);
internal Dictionary<uint, string> GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => (SteamTokenDumperPlugin.Config?.SecretDepotIDs.Contains(depotKey.Key) == false) && !string.IsNullOrEmpty(depotKey.Value) && (!SubmittedDepots.TryGetValue(depotKey.Key, out string? key) || (depotKey.Value != key))).ToDictionary(static depotKey => depotKey.Key, static depotKey => depotKey.Value);
internal Dictionary<uint, ulong> GetPackageTokensForSubmission() => PackageTokens.Where(packageToken => (SteamTokenDumperPlugin.Config?.SecretPackageIDs.Contains(packageToken.Key) == false) && (packageToken.Value > 0) && (!SubmittedPackages.TryGetValue(packageToken.Key, out ulong token) || (packageToken.Value != token))).ToDictionary(static packageToken => packageToken.Key, static packageToken => packageToken.Value);
internal static async Task<GlobalCache?> Load() {
if (!File.Exists(SharedFilePath)) {
return new GlobalCache();
}
ASF.ArchiLogger.LogGenericInfo(Strings.LoadingGlobalCache);
GlobalCache? globalCache;
try {
string json = await File.ReadAllTextAsync(SharedFilePath).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsEmpty, nameof(json)));
return null;
}
if (globalCache == null) {
ASF.ArchiLogger.LogNullError(nameof(globalCache));
globalCache = JsonConvert.DeserializeObject<GlobalCache>(json);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
return null;
}
return globalCache;
return null;
}
internal void OnPICSChanges(uint currentChangeNumber, IReadOnlyCollection<KeyValuePair<uint, SteamApps.PICSChangesCallback.PICSChangeData>> appChanges) {
if (currentChangeNumber == 0) {
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
}
if (globalCache == null) {
ASF.ArchiLogger.LogNullError(nameof(globalCache));
if (appChanges == null) {
throw new ArgumentNullException(nameof(appChanges));
}
if (currentChangeNumber <= LastChangeNumber) {
return;
}
LastChangeNumber = currentChangeNumber;
foreach ((uint appID, SteamApps.PICSChangesCallback.PICSChangeData appData) in appChanges) {
if (!AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) || (appData.ChangeNumber <= previousChangeNumber)) {
continue;
}
AppChangeNumbers.TryRemove(appID, out _);
}
Utilities.InBackground(Save);
return null;
}
internal void OnPICSChangesRestart(uint currentChangeNumber) {
if (currentChangeNumber == 0) {
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
}
ASF.ArchiLogger.LogGenericInfo(Strings.ValidatingGlobalCacheIntegrity);
if (currentChangeNumber <= LastChangeNumber) {
return;
}
if (globalCache.DepotKeys.Values.Any(static depotKey => !IsValidDepotKey(depotKey))) {
ASF.ArchiLogger.LogGenericError(Strings.GlobalCacheIntegrityValidationFailed);
LastChangeNumber = currentChangeNumber;
AppChangeNumbers.Clear();
Utilities.InBackground(Save);
return null;
}
internal bool ShouldRefreshAppInfo(uint appID) => !AppChangeNumbers.ContainsKey(appID);
internal bool ShouldRefreshDepotKey(uint depotID) => !DepotKeys.ContainsKey(depotID);
return globalCache;
}
internal void UpdateAppChangeNumbers(IReadOnlyCollection<KeyValuePair<uint, uint>> appChangeNumbers) {
if (appChangeNumbers == null) {
throw new ArgumentNullException(nameof(appChangeNumbers));
}
bool save = false;
foreach ((uint appID, uint changeNumber) in appChangeNumbers) {
if (AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) && (previousChangeNumber == changeNumber)) {
continue;
}
AppChangeNumbers[appID] = changeNumber;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
internal void OnPICSChanges(uint currentChangeNumber, IReadOnlyCollection<KeyValuePair<uint, SteamApps.PICSChangesCallback.PICSChangeData>> appChanges) {
if (currentChangeNumber == 0) {
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
}
internal void UpdateAppTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> appTokens, IReadOnlyCollection<uint> publicAppIDs) {
if (appTokens == null) {
throw new ArgumentNullException(nameof(appTokens));
}
ArgumentNullException.ThrowIfNull(appChanges);
if (publicAppIDs == null) {
throw new ArgumentNullException(nameof(publicAppIDs));
}
bool save = false;
foreach ((uint appID, ulong appToken) in appTokens) {
if (AppTokens.TryGetValue(appID, out ulong previousAppToken) && (previousAppToken == appToken)) {
continue;
}
AppTokens[appID] = appToken;
save = true;
}
foreach (uint appID in publicAppIDs) {
if (AppTokens.TryGetValue(appID, out ulong previousAppToken) && (previousAppToken == 0)) {
continue;
}
AppTokens[appID] = 0;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
if (currentChangeNumber <= LastChangeNumber) {
return;
}
internal void UpdateDepotKeys(ICollection<SteamApps.DepotKeyCallback> depotKeyResults) {
if (depotKeyResults == null) {
throw new ArgumentNullException(nameof(depotKeyResults));
LastChangeNumber = currentChangeNumber;
foreach ((uint appID, SteamApps.PICSChangesCallback.PICSChangeData appData) in appChanges) {
if (!AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) || (appData.ChangeNumber <= previousChangeNumber)) {
continue;
}
bool save = false;
foreach (SteamApps.DepotKeyCallback depotKeyResult in depotKeyResults) {
if (depotKeyResult.Result != EResult.OK) {
continue;
}
string depotKey = BitConverter.ToString(depotKeyResult.DepotKey).Replace("-", "", StringComparison.Ordinal);
if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string? previousDepotKey) && (previousDepotKey == depotKey)) {
continue;
}
DepotKeys[depotKeyResult.DepotID] = depotKey;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
AppChangeNumbers.TryRemove(appID, out _);
}
internal void UpdatePackageTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> packageTokens) {
if (packageTokens == null) {
throw new ArgumentNullException(nameof(packageTokens));
}
Utilities.InBackground(Save);
}
bool save = false;
foreach ((uint packageID, ulong packageToken) in packageTokens) {
if (PackageTokens.TryGetValue(packageID, out ulong previousPackageToken) && (previousPackageToken == packageToken)) {
continue;
}
PackageTokens[packageID] = packageToken;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
internal void OnPICSChangesRestart(uint currentChangeNumber) {
if (currentChangeNumber == 0) {
throw new ArgumentOutOfRangeException(nameof(currentChangeNumber));
}
internal void UpdateSubmittedData(IReadOnlyDictionary<uint, ulong> apps, IReadOnlyDictionary<uint, ulong> packages, IReadOnlyDictionary<uint, string> depots) {
if (apps == null) {
throw new ArgumentNullException(nameof(apps));
if (currentChangeNumber <= LastChangeNumber) {
return;
}
LastChangeNumber = currentChangeNumber;
AppChangeNumbers.Clear();
Utilities.InBackground(Save);
}
internal bool ShouldRefreshAppInfo(uint appID) => !AppChangeNumbers.ContainsKey(appID);
internal bool ShouldRefreshDepotKey(uint depotID) => !DepotKeys.ContainsKey(depotID);
internal void UpdateAppChangeNumbers(IReadOnlyCollection<KeyValuePair<uint, uint>> appChangeNumbers) {
ArgumentNullException.ThrowIfNull(appChangeNumbers);
bool save = false;
foreach ((uint appID, uint changeNumber) in appChangeNumbers) {
if (AppChangeNumbers.TryGetValue(appID, out uint previousChangeNumber) && (previousChangeNumber == changeNumber)) {
continue;
}
if (packages == null) {
throw new ArgumentNullException(nameof(packages));
}
if (depots == null) {
throw new ArgumentNullException(nameof(depots));
}
foreach ((uint appID, ulong token) in apps) {
SubmittedApps[appID] = token;
}
foreach ((uint packageID, ulong token) in packages) {
SubmittedPackages[packageID] = token;
}
foreach ((uint depotID, string key) in depots) {
SubmittedDepots[depotID] = key;
}
AppChangeNumbers[appID] = changeNumber;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
}
internal void UpdateAppTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> appTokens, IReadOnlyCollection<uint> publicAppIDs) {
ArgumentNullException.ThrowIfNull(appTokens);
ArgumentNullException.ThrowIfNull(publicAppIDs);
bool save = false;
foreach ((uint appID, ulong appToken) in appTokens) {
if (AppTokens.TryGetValue(appID, out ulong previousAppToken) && (previousAppToken == appToken)) {
continue;
}
AppTokens[appID] = appToken;
save = true;
}
foreach (uint appID in publicAppIDs) {
if (AppTokens.TryGetValue(appID, out ulong previousAppToken) && (previousAppToken == 0)) {
continue;
}
AppTokens[appID] = 0;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
}
internal void UpdateDepotKeys(ICollection<SteamApps.DepotKeyCallback> depotKeyResults) {
ArgumentNullException.ThrowIfNull(depotKeyResults);
bool save = false;
foreach (SteamApps.DepotKeyCallback depotKeyResult in depotKeyResults) {
if (depotKeyResult.Result != EResult.OK) {
continue;
}
string depotKey = Convert.ToHexString(depotKeyResult.DepotKey);
if (!IsValidDepotKey(depotKey)) {
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid, nameof(depotKey)));
continue;
}
if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string? previousDepotKey) && (previousDepotKey == depotKey)) {
continue;
}
DepotKeys[depotKeyResult.DepotID] = depotKey;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
}
internal void UpdatePackageTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> packageTokens) {
ArgumentNullException.ThrowIfNull(packageTokens);
bool save = false;
foreach ((uint packageID, ulong packageToken) in packageTokens) {
if (PackageTokens.TryGetValue(packageID, out ulong previousPackageToken) && (previousPackageToken == packageToken)) {
continue;
}
PackageTokens[packageID] = packageToken;
save = true;
}
if (save) {
Utilities.InBackground(Save);
}
}
internal void UpdateSubmittedData(IReadOnlyDictionary<uint, ulong> apps, IReadOnlyDictionary<uint, ulong> packages, IReadOnlyDictionary<uint, string> depots) {
ArgumentNullException.ThrowIfNull(apps);
ArgumentNullException.ThrowIfNull(packages);
ArgumentNullException.ThrowIfNull(depots);
foreach ((uint appID, ulong token) in apps) {
SubmittedApps[appID] = token;
}
foreach ((uint packageID, ulong token) in packages) {
SubmittedPackages[packageID] = token;
}
foreach ((uint depotID, string key) in depots) {
SubmittedDepots[depotID] = key;
}
Utilities.InBackground(Save);
}
private static bool IsValidDepotKey(string depotKey) {
if (string.IsNullOrEmpty(depotKey)) {
throw new ArgumentNullException(nameof(depotKey));
}
return (depotKey.Length == 64) && Utilities.IsValidHexadecimalText(depotKey);
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,15 +21,15 @@
using Newtonsoft.Json;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
public sealed class GlobalConfigExtension {
[JsonProperty(Required = Required.DisallowNull)]
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private set; }
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
[JsonProperty(Required = Required.DisallowNull)]
public bool SteamTokenDumperPluginEnabled { get; private set; }
public sealed class GlobalConfigExtension {
[JsonProperty(Required = Required.DisallowNull)]
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private set; }
[JsonConstructor]
internal GlobalConfigExtension() { }
}
[JsonProperty(Required = Required.DisallowNull)]
public bool SteamTokenDumperPluginEnabled { get; private set; }
[JsonConstructor]
internal GlobalConfigExtension() { }
}

View File

@@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -12,46 +11,32 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
private static System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization.Strings", typeof(Strings).Assembly);
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -60,246 +45,183 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to Finished retrieving {0} app access tokens..
/// </summary>
internal static string BotFinishedRetrievingAppAccessTokens {
get {
return ResourceManager.GetString("BotFinishedRetrievingAppAccessTokens", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished retrieving {0} app infos..
/// </summary>
internal static string BotFinishedRetrievingAppInfos {
get {
return ResourceManager.GetString("BotFinishedRetrievingAppInfos", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished retrieving {0} depot keys..
/// </summary>
internal static string BotFinishedRetrievingDepotKeys {
get {
return ResourceManager.GetString("BotFinishedRetrievingDepotKeys", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished retrieving a total of {0} app access tokens..
/// </summary>
internal static string BotFinishedRetrievingTotalAppAccessTokens {
get {
return ResourceManager.GetString("BotFinishedRetrievingTotalAppAccessTokens", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished retrieving all depot keys for a total of {0} apps..
/// </summary>
internal static string BotFinishedRetrievingTotalDepots {
get {
return ResourceManager.GetString("BotFinishedRetrievingTotalDepots", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There are no apps that require a refresh on this bot instance..
/// </summary>
internal static string BotNoAppsToRefresh {
get {
return ResourceManager.GetString("BotNoAppsToRefresh", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving {0} app access tokens....
/// </summary>
internal static string BotRetrievingAppAccessTokens {
get {
return ResourceManager.GetString("BotRetrievingAppAccessTokens", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving {0} app infos....
/// </summary>
internal static string BotRetrievingAppInfos {
get {
return ResourceManager.GetString("BotRetrievingAppInfos", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving {0} depot keys....
/// </summary>
internal static string BotRetrievingDepotKeys {
get {
return ResourceManager.GetString("BotRetrievingDepotKeys", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving a total of {0} app access tokens....
/// </summary>
internal static string BotRetrievingTotalAppAccessTokens {
get {
return ResourceManager.GetString("BotRetrievingTotalAppAccessTokens", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Retrieving all depots for a total of {0} apps....
/// </summary>
internal static string BotRetrievingTotalDepots {
get {
return ResourceManager.GetString("BotRetrievingTotalDepots", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} could not be loaded, a fresh instance will be initialized....
/// </summary>
internal static string FileCouldNotBeLoadedFreshInit {
get {
return ResourceManager.GetString("FileCouldNotBeLoadedFreshInit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} is currently disabled according to your configuration. If you&apos;d like to help SteamDB in data submission, please check out our wiki..
/// </summary>
internal static string PluginDisabledInConfig {
get {
return ResourceManager.GetString("PluginDisabledInConfig", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} has been disabled due to a missing build token.
/// </summary>
internal static string PluginDisabledMissingBuildToken {
get {
return ResourceManager.GetString("PluginDisabledMissingBuildToken", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} has been initialized successfully, thank you in advance for your help. The first submission will happen in approximately {1} from now..
/// </summary>
internal static string PluginDisabledInConfig {
get {
return ResourceManager.GetString("PluginDisabledInConfig", resourceCulture);
}
}
internal static string PluginInitializedAndEnabled {
get {
return ResourceManager.GetString("PluginInitializedAndEnabled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} initialized, the plugin will not resolve any of those: {1}..
/// </summary>
internal static string PluginSecretListInitialized {
internal static string FileCouldNotBeLoadedFreshInit {
get {
return ResourceManager.GetString("PluginSecretListInitialized", resourceCulture);
return ResourceManager.GetString("FileCouldNotBeLoadedFreshInit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The submission has failed due to too many requests sent, we&apos;ll try again in approximately {0} from now..
/// </summary>
internal static string SubmissionFailedTooManyRequests {
internal static string BotNoAppsToRefresh {
get {
return ResourceManager.GetString("SubmissionFailedTooManyRequests", resourceCulture);
return ResourceManager.GetString("BotNoAppsToRefresh", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Submitting a total of registered apps/packages/depots: {0}/{1}/{2}....
/// </summary>
internal static string SubmissionInProgress {
internal static string BotRetrievingTotalAppAccessTokens {
get {
return ResourceManager.GetString("SubmissionInProgress", resourceCulture);
return ResourceManager.GetString("BotRetrievingTotalAppAccessTokens", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not submit the data because there is no valid SteamID set that we could classify as a contributor. Consider setting up {0} property..
/// </summary>
internal static string SubmissionNoContributorSet {
internal static string BotRetrievingAppAccessTokens {
get {
return ResourceManager.GetString("SubmissionNoContributorSet", resourceCulture);
return ResourceManager.GetString("BotRetrievingAppAccessTokens", resourceCulture);
}
}
internal static string BotFinishedRetrievingAppAccessTokens {
get {
return ResourceManager.GetString("BotFinishedRetrievingAppAccessTokens", resourceCulture);
}
}
internal static string BotFinishedRetrievingTotalAppAccessTokens {
get {
return ResourceManager.GetString("BotFinishedRetrievingTotalAppAccessTokens", resourceCulture);
}
}
internal static string BotRetrievingTotalDepots {
get {
return ResourceManager.GetString("BotRetrievingTotalDepots", resourceCulture);
}
}
internal static string BotRetrievingAppInfos {
get {
return ResourceManager.GetString("BotRetrievingAppInfos", resourceCulture);
}
}
internal static string BotFinishedRetrievingAppInfos {
get {
return ResourceManager.GetString("BotFinishedRetrievingAppInfos", resourceCulture);
}
}
internal static string BotRetrievingDepotKeys {
get {
return ResourceManager.GetString("BotRetrievingDepotKeys", resourceCulture);
}
}
internal static string BotFinishedRetrievingDepotKeys {
get {
return ResourceManager.GetString("BotFinishedRetrievingDepotKeys", resourceCulture);
}
}
internal static string BotFinishedRetrievingTotalDepots {
get {
return ResourceManager.GetString("BotFinishedRetrievingTotalDepots", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There is no new data to submit, everything is up-to-date..
/// </summary>
internal static string SubmissionNoNewData {
get {
return ResourceManager.GetString("SubmissionNoNewData", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The data has been successfully submitted. The server has registered a total of new apps/packages/depots: {0} ({1} verified)/{2} ({3} verified)/{4} ({5} verified)..
/// </summary>
internal static string SubmissionNoContributorSet {
get {
return ResourceManager.GetString("SubmissionNoContributorSet", resourceCulture);
}
}
internal static string SubmissionInProgress {
get {
return ResourceManager.GetString("SubmissionInProgress", resourceCulture);
}
}
internal static string SubmissionFailedTooManyRequests {
get {
return ResourceManager.GetString("SubmissionFailedTooManyRequests", resourceCulture);
}
}
internal static string SubmissionSuccessful {
get {
return ResourceManager.GetString("SubmissionSuccessful", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New apps: {0}.
/// </summary>
internal static string SubmissionSuccessfulNewApps {
get {
return ResourceManager.GetString("SubmissionSuccessfulNewApps", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New depots: {0}.
/// </summary>
internal static string SubmissionSuccessfulNewDepots {
get {
return ResourceManager.GetString("SubmissionSuccessfulNewDepots", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New packages: {0}.
/// </summary>
internal static string SubmissionSuccessfulNewPackages {
get {
return ResourceManager.GetString("SubmissionSuccessfulNewPackages", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Verified apps: {0}.
/// </summary>
internal static string SubmissionSuccessfulVerifiedApps {
get {
return ResourceManager.GetString("SubmissionSuccessfulVerifiedApps", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Verified depots: {0}.
/// </summary>
internal static string SubmissionSuccessfulNewPackages {
get {
return ResourceManager.GetString("SubmissionSuccessfulNewPackages", resourceCulture);
}
}
internal static string SubmissionSuccessfulVerifiedPackages {
get {
return ResourceManager.GetString("SubmissionSuccessfulVerifiedPackages", resourceCulture);
}
}
internal static string SubmissionSuccessfulNewDepots {
get {
return ResourceManager.GetString("SubmissionSuccessfulNewDepots", resourceCulture);
}
}
internal static string SubmissionSuccessfulVerifiedDepots {
get {
return ResourceManager.GetString("SubmissionSuccessfulVerifiedDepots", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Verified packages: {0}.
/// </summary>
internal static string SubmissionSuccessfulVerifiedPackages {
internal static string PluginSecretListInitialized {
get {
return ResourceManager.GetString("SubmissionSuccessfulVerifiedPackages", resourceCulture);
return ResourceManager.GetString("PluginSecretListInitialized", resourceCulture);
}
}
internal static string LoadingGlobalCache {
get {
return ResourceManager.GetString("LoadingGlobalCache", resourceCulture);
}
}
internal static string ValidatingGlobalCacheIntegrity {
get {
return ResourceManager.GetString("ValidatingGlobalCacheIntegrity", resourceCulture);
}
}
internal static string GlobalCacheIntegrityValidationFailed {
get {
return ResourceManager.GetString("GlobalCacheIntegrityValidationFailed", resourceCulture);
}
}
}

View File

@@ -62,6 +62,50 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>{0} byl zakázán z důvodu chybějícího tokenu</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginDisabledInConfig" xml:space="preserve">
<value>{0} je v současné době v souladu s vaší konfigurací zakázán. Pokud byste chtěli pomoci SteamDB při odesílání dat, podívejte se na naši 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} byl úspěšně inicializován, předem vám děkujeme za vaši pomoc. První příspěvek se od teď stane přibližně za {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} nelze načíst, nová instance bude inicializována...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Neexistují žádné aplikace, které by vyžadovaly aktualizaci této instance bota.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Načítám celkem {0} přístupových tokenů...</value>
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
</data>
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
<value>Načítání {0} přístupových tokenů...</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
<value>Načítání {0} přístupových tokenů bylo dokončeno.</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Dokončeno načítání celkem {0} přístupových tokenů.</value>
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Získávání {0} informací o aplikaci...</value>
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
<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>
@@ -70,23 +114,32 @@
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Nové aplikace: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
<value>Ověřené aplikace: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
<value>Nové balíčky: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Ověřené balíčky: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>Načítání globální mezipaměti STD...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Ověřování globální integrity STD keše...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Ověření globální integrity STD keše se nezdařilo. To naznačuje, že může dojít k poškození souboru/paměti, místo toho bude inicializována nová instance.</value>
</data>
</root>

View File

@@ -67,46 +67,46 @@
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginDisabledInConfig" xml:space="preserve">
<value>{0} ist gemäß Ihrer Konfiguration derzeit deaktiviert. Wenn Sie SteamDB bei der Datenübermittlung helfen möchten, sehen Sie sich bitte unser Wiki an.</value>
<value>{0} ist gemäß Ihrer Konfiguration derzeit deaktiviert. Wenn Sie SteamDB bei der Daten-Sammlung helfen möchten, sehen Sie sich bitte unser Wiki an.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginInitializedAndEnabled" xml:space="preserve">
<value>{0} wurde erfolgreich initialisiert, wir danken Ihnen im Voraus für Ihre Hilfe. Die erste Übermittlung wird in etwa {1} ab jetzt erfolgen.</value>
<value>{0} wurde erfolgreich initialisiert. Wir danken Ihnen im Voraus für Ihre Hilfe. Die erste Übermittlung wird in etwa {1} ab jetzt erfolgen.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
<value>{0} konnte nicht geladen werden. Eine frische Instanz wird initialisiert werden...</value>
<value>{0} konnte nicht geladen werden. Eine frische Instanz wird initialisiert...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Es gibt auf dieser Bot-Instanz keine Applikationen, die einer Auffrischung bedürfen.</value>
<value>Es gibt auf dieser Bot-Instanz keine Apps, die einer Aktualisierung bedürfen.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Rufe insgesamt {0} Applikationszugriffstoken ab...</value>
<value>Rufe insgesamt {0} App-Zugriffstoken ab...</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>Rufe {0} Applikationszugriffstoken ab...</value>
<value>Rufe {0} App-Zugriffstoken ab...</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>Abruf von {0} Applikationszugrifsstoken fertiggestellt.</value>
<value>Abruf von {0} App-Zugrifsstoken abgeschlossen.</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>Abruf von insgesamt {0} Applikationszugriffstoken fertiggestellt.</value>
<value>Abruf von insgesamt {0} App-Zugriffstoken abgeschlossen.</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>Rufe alle Depots für insgesamt {0} Applikationen ab...</value>
<value>Abruf aller Depots für insgesamt {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>Rufe {0} Applikationsinfos ab...</value>
<value>Rufe {0} App-Infos ab...</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>Abruf von {0} Applikationsinfos fertiggestellt.</value>
<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="BotRetrievingDepotKeys" xml:space="preserve">
@@ -114,38 +114,38 @@
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Abruf von {0} Depotschlüsseln fertiggestellt.</value>
<value>Abruf von {0} Depotschlüsseln abgeschlossen.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Abruf aller Depotschlüssel für insgesamt {0} Applikationen fertiggestellt.</value>
<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>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Es sind keine neuen Daten einzureichen. Alle Daten sind aktuell.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Konnte keine Daten einreichen, da keine SteamID identifiziert wurde, die als Einreichender valide ist. Bitte ziehe in Betracht {0} zu konfigurieren.</value>
<value>Konnte keine Daten spenden, da keine SteamID identifiziert wurde, die als Spender valide ist. Bitte ziehe in Betracht {0} zu konfigurieren.</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>Reiche insgesamt folgende Applikationen/Pakete/Depots ein: {0}/{1}/{2}...</value>
<value>Übermittle insgesamt folgende Apps/Pakete/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>Das Einreichen schlug fehl, weil wir zu viele Einreichungen in zu kurzer Zeit versuchten. Wir versuchen es in ungefähr {0} wieder.</value>
<value>Das Übermitteln schlug fehl, weil wir zu viele Spenden in zu kurzer Zeit versuchten. Wir versuchen es in ungefähr {0} wieder.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Die Daten wurden erfolgreich übermittelt. Der Server hat insgesamt folgende Anzahl an neuen Applikationen/Paketen/Depots registriert: {0} ({1} verifiziert)/{2} ({3} verifiziert)/{4} ({5} verifiziert).</value>
<value>Die Daten wurden erfolgreich übermittelt. Der Server hat insgesamt folgende Anzahl an neuen Apps/Paketen/Depots registriert: {0} ({1} verifiziert)/{2} ({3} verifiziert)/{4} ({5} verifiziert).</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>Neue Applikationen: {0}</value>
<value>Neue 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>Verifizierte Applikationen: {0}</value>
<value>Verifizierte 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">
@@ -165,7 +165,12 @@
<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} wurde konfiguriert, das Plugin wird keinen der folgenden Werte auflösen: {1}.</value>
<value>{0} wurde initialisiert, das Plugin wird keinen der folgenden Werte verarbeiten: {1}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>Globaler STD-Cache wird geladen...</value>
</data>
</root>

View File

@@ -62,31 +62,119 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>Το {0} έχει απενεργοποιηθεί λόγω ενός διακριτικού κατασκευής που λείπει</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginDisabledInConfig" xml:space="preserve">
<value>{0} είναι απενεργοποιημένο σύμφωνα με τις ρυθμίσεις σας. Αν θέλετε να βοηθήσετε το SteamDB στην υποβολή δεδομένων, παρακαλώ ελέγξτε το wiki μας.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginInitializedAndEnabled" xml:space="preserve">
<value>{0} έχει αρχικοποιηθεί με επιτυχία, σας ευχαριστώ εκ των προτέρων για τη βοήθειά σας. Η πρώτη υποβολή θα γίνει σε περίπου {1} από τώρα.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
<value>{0} δεν μπόρεσε να φορτωθεί, μια νέα παρουσία θα αρχικοποιηθεί...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Δεν υπάρχουν εφαρμογές που να απαιτούν ανανέωση σε αυτό το bot instance.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Ανάκτηση συνολικά {0} διακριτικών πρόσβασης εφαρμογής...</value>
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
</data>
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
<value>Ανάκτηση {0} διακριτικών πρόσβασης εφαρμογής...</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
<value>Ολοκληρώθηκε η ανάκτηση {0} διακριτικών πρόσβασης εφαρμογών.</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Ολοκληρώθηκε η ανάκτηση {0} διακριτικών πρόσβασης εφαρμογών.</value>
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
</data>
<data name="BotRetrievingTotalDepots" xml:space="preserve">
<value>Ανάκτηση όλων των αποθηκών για συνολικά {0} εφαρμογές...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Ανάκτηση {0} πληροφοριών εφαρμογής...</value>
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
<value>Ολοκληρώθηκε η ανάκτηση {0} πληροφοριών εφαρμογών.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotRetrievingDepotKeys" xml:space="preserve">
<value>Ανάκτηση {0} κλειδιών αποθήκης...</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Ολοκληρώθηκε η ανάκτηση {0} κλειδιών αποθήκευσης.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Ολοκληρώθηκε η ανάκτηση όλων των κλειδιών αποθηκών για συνολικά {0} εφαρμογές.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Δεν υπάρχουν νέα δεδομένα για να υποβάλετε, όλα είναι ενημερωμένα.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Δεν ήταν δυνατή η υποβολή των δεδομένων επειδή δεν υπάρχει έγκυρο σύνολο SteamID που θα μπορούσαμε να ταξινομήσουμε ως συνεισφέροντα. Εξετάστε το ενδεχόμενο δημιουργίας {0} ιδιότητας.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SteamOwnerID") that the user is expected to set</comment>
</data>
<data name="SubmissionInProgress" xml:space="preserve">
<value>Υποβολή ενός συνόλου καταχωρημένων εφαρμογών/πακέτων/αποθηκών χρημάτων: {0}/{1}/{2}...</value>
<comment>{0} will be replaced by the number of app access tokens being submitted, {1} will be replaced by the number of package access tokens being submitted, {2} will be replaced by the number of depot keys being submitted</comment>
</data>
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
<value>Η υποβολή απέτυχε λόγω πάρα πολλών αιτήσεων που στάλθηκαν, θα προσπαθήσουμε ξανά σε περίπου {0} από τώρα.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Τα δεδομένα έχουν υποβληθεί με επιτυχία. Ο διακομιστής έχει καταχωρήσει συνολικά νέες εφαρμογές/πακέτα/depots: {0} ({1} επαληθευμένο)/{2} ({3} επαληθεύτηκε)/{4} ({5} επιβεβαιωμένο).</value>
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
</data>
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Νέες εφαρμογές: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
<value>Επαληθευμένες εφαρμογές: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
<value>Νέα πακέτα: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Επαληθευμένα πακέτα: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
<value>Νέες αποθήκες: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
<value>Επαληθευμένες αποθήκες: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="PluginSecretListInitialized" xml:space="preserve">
<value>{0} αρχικοποιήθηκε, το plugin δεν θα επιλύσει κανένα από αυτά: {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>Φόρτωση καθολικής μνήμης cache...</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

@@ -168,4 +168,13 @@
<value>{0} iniciado, el plugin no analizará ninguno de los siguientes: {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>Cargando caché global de STD... </value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validando integridad de la caché global de STD... </value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>No se pudo verificar la integridad de la caché global de STD. Esto puede significar una potencial corrupción de archivo/memoria, se iniciará una nueva instancia. </value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<value>{0} initialisé, le plugin ne résoudra aucun de ceux-ci : {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>Chargement du cache STD global...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validation de l'intégrité du cache STD global...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Impossible de vérifier l'intégrité du cache STD global. Cela peut être due à une corruption potentielle de fichier/mémoire, une nouvelle instance va être créée.</value>
</data>
</root>

View File

@@ -62,31 +62,119 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>{0} è stato disabilitato a causa di un token di generazione mancante</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} è attualmente disabilitato in base alla tua configurazione. Se desideri aiutare SteamDB nell'invio dei dati, controlla la nostra 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} è stato inizializzato con successo, grazie in anticipo per il tuo aiuto. Il primo invio avverrà in circa {1} da ora.</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} non può essere caricato, una nuova richiesta verrà iniziata...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Non ci sono applicazioni che richiedono un aggiornamento su questa richiesta del bot.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Ricezione di un totale di {0} token di accesso all'app...</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>Ricezione di {0} token di accesso all'app...</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>Hai completato il recupero di {0} token di accesso all'app.</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>Hai completato il recupero di un totale di {0} token di accesso all'app.</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>Ricezione di tutti i depositi per un totale di {0} app...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Recuperate {0} informazioni app...</value>
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
<value>Hai completato il recupero di {0} informazioni app.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotRetrievingDepotKeys" xml:space="preserve">
<value>Recupero {0} chiavi...</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Completato il recupero di {0} chiavi.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Finito il recupero di tutte le chiavi del deposito per un totale di {0} applicazioni.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Non ci sono nuovi dati da inviare, tutto è aggiornato.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Impossibile inviare i dati perché non c'è uno SteamID impostato valido che potremmo classificare come contributore. Considera di impostare delle proprietà {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>Inviando un totale di app/pacchetti/depositi registrati: {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>L'invio non è riuscito a causa di troppe richieste inviate, riproveremo tra circa {0} da ora.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>I dati sono stati inviati con successo. Il server ha registrato un totale di nuove app/pacchetti/depositi: {0} ({1} verificato)/{2} ({3} verificato)/{4} ({5} verificato).</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>Nuove app: {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>App verificate: {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>Nuovi pacchetti: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Pacchetti verificati: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
<value>Nuove app: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
<value>App verificate: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="PluginSecretListInitialized" xml:space="preserve">
<value>{0} inizializzato, il plugin non risolverà nessuno dei seguenti: {1}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>Caricamento cache globale STD...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Convalida integrità cache globale STD...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Impossibile verificare l'integrità globale della cache STD. Questo suggerisce un potenziale danneggiamento di file/memoria, una nuova istanza verrà inizializzata.</value>
</data>
</root>

View File

@@ -62,31 +62,87 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>{0} buvo išjungtas, dėl trūkstamos dalies</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} buvo sėkmingai įrašytas, dėkojame už jūsų pagalbą. Pirma pateiktis įvyks už maždaug {1} nuo dabar.</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} nepavyko užkrauti, bus įrašyta nauja instancija...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Šiame robote nėra jokių programų, kurias reikėtų atnaujinti.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Iš viso gaunama {0} programos prieigos raktų...</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>Gaunama {0} programos prieigos raktų...</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>Baigta gauti {0} programos prieigos raktų.</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>Iš viso baigta gauti {0} programos prieigos raktų.</value>
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Gaunama {0} programos informacijos...</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>Baigta gauti {0} programos informacijos.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Nėra jokių naujų duomenų, kuriuos būtų galima pateikti, viskas jau atnaujinta.</value>
</data>
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
<value>Pateiktis nepavyko dėl per daug išsiustų prašymų, pradėsime iš naujo už maždaug {0} nuo dabar.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Naujos programos: {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>Patvirtintos programos: {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>Nauji paketai: {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>Patvirtinti paketai: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="PluginSecretListInitialized" xml:space="preserve">
<value>{0} inicijuota, įskiepis neišspręs nė vieno iš šių dalykų: {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>Kraunama STD global talpykla...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Tvirtinamas STD global talpyklos vientisumas...</value>
</data>
</root>

View File

@@ -82,7 +82,22 @@
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Jaunas aplikācijas: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
<value>Pārbaudītas aplikācijas: {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>Jaunas pakas: {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>Pārbaudītas pakas: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>

View File

@@ -168,4 +168,13 @@
<value>{0} zainicjowano, wtyczka nie rozwiąże żadnego z tych: {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>Ładowanie globalnej pamięci podręcznej STD...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Sprawdzanie integralność globalnej pamięci podręcznej STD...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Nie udało się zweryfikować integralności globalnej pamięci podręcznej STD. Sugeruje to potencjalne uszkodzenie pliku/pamięci, zamiast tego zostanie zainicjowana nowa instancja.</value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<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 STD global...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validando integridade do cache STD global...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Falha ao verificar a integridade do cache STD global. Isso sugere uma potencial corrupção de arquivo/memória, uma instância nova será inicializada em vez disso.</value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<value>{0} INITIALIZD, TEH PLUGIN WILL NOT RESOLVE ANY OV DOSE: {1}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>LOADIN STD GLOBAL CACHE...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>VALIDATIN STD GLOBAL CACHE INTEGRITY...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>FAILD 2 VERIFY STD GLOBAL CACHE INTEGRITY. DIS SUGGESTS POTENTIAL FILE/MEMS CORRUPSHUN, FRESH INSTANCE WILL BE INITIALIZD INSTEAD.</value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<value>{0} initialized, the plugin will not resolve any of those: {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>Loading STD global cache...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Validating STD global cache integrity...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Failed to verify STD global cache integrity. This suggests a potential file/memory corruption, a fresh instance will be initialized instead.</value>
</data>
</root>

View File

@@ -81,40 +81,100 @@
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Нет приложений, которые требуют обновления для этого бота.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Получение всего {0} токенов доступа приложений...</value>
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
</data>
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
<value>Получение {0} токенов доступа приложения...</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
<value>Закончено получение {0} токенов доступа приложения.</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Закончено получение суммарно {0} токенов доступа приложения.</value>
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
</data>
<data name="BotRetrievingTotalDepots" xml:space="preserve">
<value>Получение всех хранилищ {0} приложений...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Получение информации {0} приложений...</value>
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
<value>Завершено получение информации {0} приложений.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotRetrievingDepotKeys" xml:space="preserve">
<value>Получение {0} ключей хранилища...</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Получение {0} ключей хранилища завершено.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Получение всех ключей хранилища {0} приложений завершено.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Нет новых данных для отправки, всё актуально.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Ошибка отправки данных: корректный SteamID не был предоставлен. Проверьте правильность настройки {0}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SteamOwnerID") that the user is expected to set</comment>
</data>
<data name="SubmissionInProgress" xml:space="preserve">
<value>Отправка зарегистрированных приложений/пакетов/хранилищ: {0}/{1}/{2}...</value>
<comment>{0} will be replaced by the number of app access tokens being submitted, {1} will be replaced by the number of package access tokens being submitted, {2} will be replaced by the number of depot keys being submitted</comment>
</data>
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
<value>Отправка не удалась из-за слишком большого количества отправленных запросов, мы попытаемся повторить попытку примерно через {0}.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Данные успешно отправлены. Общее количество зарегистрированных сервером приложений/пакетов/хранилищ: {0} ({1} из их - подтверждены)/{2} ({3} из них - подтверждены)/{4} ({5} из них - подтверждены).</value>
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
</data>
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Новые приложения: {0}</value>
<value>Новых приложений: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
<value>Проверенные приложения: {0}</value>
<value>Проверенных приложений: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
<value>Новые пакеты: {0}</value>
<value>Новых пакетов: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Проверенные пакеты: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
<value>Новые хранилища: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
<value>Количество подтвержденных хранилищ: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>
<data name="PluginSecretListInitialized" xml:space="preserve">
<value>{0} инициализирован, плагин не взаимодействует ни с одним из их: {1}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>Загрузка глобального кэша STD...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Проверка целостности глобального кэша STD...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Не удалось проверить целостность глобального кэша STD. Это говорит о потенциальном повреждении файла/памяти, вместо этого будет инициализирован новый экземпляр.</value>
</data>
</root>

View File

@@ -62,30 +62,112 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>{0} bol vypnutý kvôli chýbajúcemu build tokenu</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} bol na základe vašej konfigurácie vypnutý. Ak máte záujem pomôcť so zberom údajov pre SteamDB, navštívte našu 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} bol úspešne spustený. Týmto vám ďakujeme za vašu pomoc. Prvý zápis sa udej od teraz za {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} sa nedokázal načítať, bude načítaný nanovo...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Tento bot neobsahuje žiadne aplikácie na opätovné overenie.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Celkovo získaných {0} označení aplikácií...</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>Získaných {0} označení aplikácií...</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>Získalo sa {0} označení aplikácií.</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>Celkovo sa získalo {0} označení aplikácií.</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>Získavanie všetkých {0} položiek pre aplikácie...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Získavam informácie o {0} aplikáciách...</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>Dokončené získavanie informácií {0} aplikácií.</value>
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
</data>
<data name="BotRetrievingDepotKeys" xml:space="preserve">
<value>Získavam {0} kľúčov položiek...</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Dokončené získavanie {0} kľúčov položiek.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Dokončené získanie všetkých kľúčov položiek {0} aplikácií.</value>
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Dáta sú aktuálne, nič nové na potvrdenie.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Nemôžeme prijať žiadne záznamy, pretože ani jedno SteamID nezodpovedá definícii prispievateľa. Zvážte nastavenie {0} podľa pokynov.</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>Prijali sme celkovo registrovaných aplikácií/balíčkov/položiek:{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>Prijímanie bolo neúspešné kvôli veľkému počtu zaslaných požiadaviek. Pokus zopakujeme za približne {0} od teraz.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Dáta boli úspešne prijaté. Na serveri boli celkovo registrovaných nových aplikácií/balíčkov/položiek: {0} ({1} overených)/{2} ({3} overených)/{4} ({5} overených).</value>
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
</data>
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Nové aplikácie: {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>Overené aplikácie: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
<value>Nové balíčky: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Overené balíčky: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
<value>Nové položky: {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>Overené položky: {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} inicializovaných, modul nebude pracovať so žiadnym: {1}.</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns="" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
</root>

View File

@@ -168,4 +168,13 @@
<value>{0} başlatıldı, eklenti şunlardan hiçbirini çözemedi: {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 küresel önbelleği yükleniyor...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>STD küresel önbellek bütünlüğü doğrulanıyor...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>STD küresel önbellek bütünlüğü doğrulanamadı. Bu, olası bir dosya/bellek bozulması olduğunu gösterir, bunun yerine yeni bir örnek başlatılacaktır.</value>
</data>
</root>

View File

@@ -74,24 +74,68 @@
<value>{0} đã được khởi tạo thành công, cảm ơn bạn rất nhiều vì sự giúp đỡ. Lần gửi đầu tiên sẽ xảy ra trong khoảng {1} kể từ bây giờ.</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>Không thể tải {0}, một trạng thái mới sẽ được khởi tạo...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Không có ứng dụng nào yêu cầu làm mới cho trạng thái của bot này.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Đang thu nhận tổng số {0} mã truy cập ứng dụng...</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>Đang thu nhận {0} mã truy cập ứng dụng...</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>Đã hoàn tất thu nhận {0} mã truy cập ứng dụng.</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>Đã hoàn tất thu nhận tổng số {0} mã truy cập ứng dụng.</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>Đang thu nhận tất cả kho của tổng số {0} ứng dụng...</value>
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
</data>
<data name="BotRetrievingAppInfos" xml:space="preserve">
<value>Đang 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 being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
<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="BotRetrievingDepotKeys" xml:space="preserve">
<value>Đang thu nhận {0} khóa kho...</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
</data>
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>Đã hoàn tất thu nhận {0} khóa kho.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
<value>Đã 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>
</data>
<data name="SubmissionNoNewData" xml:space="preserve">
<value>Không có dữ liệu mới để gửi, mọi thứ đều đã được cập nhật.</value>
</data>
<data name="SubmissionNoContributorSet" xml:space="preserve">
<value>Không thể gửi dữ liệu vì không có bộ SteamID hợp lệ mà chúng tôi có thể phân loại là người đóng góp. Hãy xem xét thiết lập thuộc tính {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>Đang gửi tổng số các ứng dụng/gói/kho đã đăng ký: {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>Việc gửi đã thất bại do có quá nhiều yêu cầu được gửi, chúng tôi sẽ thử lại sau khoảng {0} kể từ bây giờ.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="SubmissionSuccessful" xml:space="preserve">
<value>Dữ liệu đã được gửi thành công. Máy chủ đã đăng ký tổng số ứng dụng/gói/kho mới: {0} ({1} đã xác thực)/{2} ({3} đã xác thực)/{4} ({5} đã xác thực).</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>
@@ -120,5 +164,17 @@
<value>Kho đã xác thực: {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} đã được khởi tạo, plugin sẽ không can thiệp với những thứ sau: {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>Đang tải bộ nhớ đệm STD chung...</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>Đang kiểm tra trạng thái bộ nhớ đệm STD chung...</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>Không thể xác minh trạng thái bộ nhớ đệm chung STD. Điều này có khả năng do tệp/bộ nhớ bị hỏng, một trạng thái mới sẽ được khởi tạo thay thế.</value>
</data>
</root>

View File

@@ -168,4 +168,13 @@
<value>{0} 已初始化,插件无法解析以下任何内容:{1}。</value>
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
</data>
<data name="LoadingGlobalCache" xml:space="preserve">
<value>正在加载 STD 全局缓存……</value>
</data>
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
<value>正在验证 STD 全局缓存完整性……</value>
</data>
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
<value>验证 STD 全局缓存完整性失败。这意味着潜在的文件或内存损坏,即将初始化新实例作为替代。</value>
</data>
</root>

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,53 +27,45 @@ using ArchiSteamFarm.Core;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal sealed class RequestData {
[JsonProperty(PropertyName = "guid", Required = Required.Always)]
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
[JsonProperty(PropertyName = "token", Required = Required.Always)]
private static string Token => SharedInfo.Token;
internal sealed class RequestData {
[JsonProperty(PropertyName = "guid", Required = Required.Always)]
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
[JsonProperty(PropertyName = "v", Required = Required.Always)]
private static byte Version => SharedInfo.ApiVersion;
[JsonProperty(PropertyName = "token", Required = Required.Always)]
private static string Token => SharedInfo.Token;
[JsonProperty(PropertyName = "apps", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Apps;
[JsonProperty(PropertyName = "v", Required = Required.Always)]
private static byte Version => SharedInfo.ApiVersion;
[JsonProperty(PropertyName = "depots", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Depots;
[JsonProperty(PropertyName = "apps", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Apps;
private readonly ulong SteamID;
[JsonProperty(PropertyName = "depots", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Depots;
[JsonProperty(PropertyName = "subs", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Subs;
private readonly ulong SteamID;
[JsonProperty(PropertyName = "steamid", Required = Required.Always)]
private string SteamIDText => new SteamID(SteamID).Render();
[JsonProperty(PropertyName = "subs", Required = Required.Always)]
private readonly ImmutableDictionary<string, string> Subs;
internal RequestData(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
throw new ArgumentOutOfRangeException(nameof(steamID));
}
[JsonProperty(PropertyName = "steamid", Required = Required.Always)]
private string SteamIDText => new SteamID(SteamID).Render();
if (apps == null) {
throw new ArgumentNullException(nameof(apps));
}
if (accessTokens == null) {
throw new ArgumentNullException(nameof(accessTokens));
}
if (depots == null) {
throw new ArgumentNullException(nameof(depots));
}
SteamID = steamID;
Apps = apps.ToImmutableDictionary(app => app.Key.ToString(CultureInfo.InvariantCulture), app => app.Value.ToString(CultureInfo.InvariantCulture));
Subs = accessTokens.ToImmutableDictionary(package => package.Key.ToString(CultureInfo.InvariantCulture), package => package.Value.ToString(CultureInfo.InvariantCulture));
Depots = depots.ToImmutableDictionary(depot => depot.Key.ToString(CultureInfo.InvariantCulture), depot => depot.Value);
internal RequestData(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
throw new ArgumentOutOfRangeException(nameof(steamID));
}
ArgumentNullException.ThrowIfNull(apps);
ArgumentNullException.ThrowIfNull(accessTokens);
ArgumentNullException.ThrowIfNull(depots);
SteamID = steamID;
Apps = apps.ToImmutableDictionary(static app => app.Key.ToString(CultureInfo.InvariantCulture), static app => app.Value.ToString(CultureInfo.InvariantCulture));
Subs = accessTokens.ToImmutableDictionary(static package => package.Key.ToString(CultureInfo.InvariantCulture), static package => package.Value.ToString(CultureInfo.InvariantCulture));
Depots = depots.ToImmutableDictionary(static depot => depot.Key.ToString(CultureInfo.InvariantCulture), static depot => depot.Value);
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,45 +23,45 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class ResponseData {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class ResponseData {
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
[JsonProperty(PropertyName = "data", Required = Required.DisallowNull)]
internal readonly InternalData? Data;
[JsonProperty(PropertyName = "data", Required = Required.DisallowNull)]
internal readonly InternalData? Data;
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly bool Success;
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly bool Success;
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
[JsonConstructor]
private ResponseData() { }
internal sealed class InternalData {
[JsonProperty(PropertyName = "new_apps", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "new_depots", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "new_subs", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_apps", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_depots", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_subs", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
[JsonConstructor]
private ResponseData() { }
internal sealed class InternalData {
[JsonProperty(PropertyName = "new_apps", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "new_depots", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "new_subs", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_apps", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_depots", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
[JsonProperty(PropertyName = "verified_subs", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
[JsonConstructor]
private InternalData() { }
}
private InternalData() { }
}
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
}
#pragma warning restore CA1812 // False positive, the class is used during json deserialization

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,17 +19,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal static class SharedInfo {
internal const byte ApiVersion = 2;
internal const byte AppInfosPerSingleRequest = byte.MaxValue;
internal const byte MaximumHoursBetweenRefresh = 8; // Per single bot account, makes sense to be 2 or 3 times less than MinimumHoursBetweenUploads
internal const byte MaximumMinutesBeforeFirstUpload = 60; // Must be greater or equal to MinimumMinutesBeforeFirstUpload
internal const byte MinimumHoursBetweenUploads = 24;
internal const byte MinimumMinutesBeforeFirstUpload = 10; // Must be less or equal to MaximumMinutesBeforeFirstUpload
internal const string ServerURL = "https://asf-token-dumper.xpaw.me";
internal const string Token = "STEAM_TOKEN_DUMPER_TOKEN"; // This is filled automatically during our CI build with API key provided by xPaw for ASF project
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
internal static bool HasValidToken => Token.Length == 128;
}
internal static class SharedInfo {
internal const byte ApiVersion = 2;
internal const byte AppInfosPerSingleRequest = byte.MaxValue;
internal const byte MaximumHoursBetweenRefresh = 8; // Per single bot account, makes sense to be 2 or 3 times less than MinimumHoursBetweenUploads
internal const byte MaximumMinutesBeforeFirstUpload = 60; // Must be greater or equal to MinimumMinutesBeforeFirstUpload
internal const byte MinimumHoursBetweenUploads = 24;
internal const byte MinimumMinutesBeforeFirstUpload = 10; // Must be less or equal to MaximumMinutesBeforeFirstUpload
internal const string ServerURL = "https://asf-token-dumper.xpaw.me";
internal const string Token = "STEAM_TOKEN_DUMPER_TOKEN"; // This is filled automatically during our CI build with API key provided by xPaw for ASF project
internal static bool HasValidToken => Token.Length == 128;
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,28 +24,28 @@ using System.Diagnostics.CodeAnalysis;
using ArchiSteamFarm.IPC.Integration;
using Newtonsoft.Json;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class SteamTokenDumperConfig {
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; internal set; }
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretAppIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class SteamTokenDumperConfig {
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; internal set; }
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretDepotIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretAppIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretDepotIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[JsonProperty(Required = Required.DisallowNull)]
public bool SkipAutoGrantPackages { get; private set; }
[JsonProperty(Required = Required.DisallowNull)]
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
[JsonConstructor]
internal SteamTokenDumperConfig() { }
}
[JsonProperty(Required = Required.DisallowNull)]
public bool SkipAutoGrantPackages { get; private set; }
[JsonConstructor]
internal SteamTokenDumperConfig() { }
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,12 +24,12 @@ using ArchiSteamFarm.IPC.Controllers.Api;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
[Route("Api/SteamTokenDumperPlugin")]
public sealed class SteamTokenDumperController : ArchiController {
[HttpGet(nameof(GlobalConfigExtension))]
[ProducesResponseType(typeof(GlobalConfigExtension), (int) HttpStatusCode.OK)]
[SwaggerOperation(Tags = new[] { nameof(GlobalConfigExtension) })]
public ActionResult<GlobalConfigExtension> Get() => Ok(new GlobalConfigExtension());
}
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
[Route("Api/SteamTokenDumperPlugin")]
public sealed class SteamTokenDumperController : ArchiController {
[HttpGet(nameof(GlobalConfigExtension))]
[ProducesResponseType(typeof(GlobalConfigExtension), (int) HttpStatusCode.OK)]
[SwaggerOperation(Tags = new[] { nameof(GlobalConfigExtension) })]
public ActionResult<GlobalConfigExtension> Get() => Ok(new GlobalConfigExtension());
}

View File

@@ -5,6 +5,7 @@
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if NETFRAMEWORK
using ArchiSteamFarm.Compatibility;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
@@ -29,487 +26,480 @@ using ArchiSteamFarm.Steam.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static ArchiSteamFarm.Steam.Bot;
namespace ArchiSteamFarm.Tests {
[TestClass]
public sealed class Bot {
[TestMethod]
public void MaxItemsBarelyEnoughForOneSet() {
const uint relevantAppID = 42;
namespace ArchiSteamFarm.Tests;
Dictionary<uint, byte> itemsPerSet = new() {
{ relevantAppID, MinCardsPerBadge },
{ 43, MinCardsPerBadge + 1 }
};
[TestClass]
public sealed class Bot {
[TestMethod]
public void MaxItemsBarelyEnoughForOneSet() {
const uint relevantAppID = 42;
HashSet<Asset> items = new();
Dictionary<uint, byte> itemsPerSet = new() {
{ relevantAppID, MinCardsPerBadge },
{ 43, MinCardsPerBadge + 1 }
};
foreach ((uint appID, byte cards) in itemsPerSet) {
for (byte i = 1; i <= cards; i++) {
items.Add(CreateCard(i, appID));
}
HashSet<Asset> items = new();
foreach ((uint appID, byte cards) in itemsPerSet) {
for (byte i = 1; i <= cards; i++) {
items.Add(CreateCard(i, appID));
}
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet, MinCardsPerBadge);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = items.Where(item => item.RealAppID == relevantAppID)
.GroupBy(item => (item.RealAppID, item.ContextID, item.ClassID))
.ToDictionary(grouping => grouping.Key, grouping => (uint) grouping.Sum(item => item.Amount));
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void MaxItemsTooSmall() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet, MinCardsPerBadge);
GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1);
Assert.Fail();
}
[TestMethod]
public void MoreCardsThanNeeded() {
const uint appID = 42;
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = items.Where(static item => item.RealAppID == relevantAppID).GroupBy(static item => (item.RealAppID, item.ContextID, item.ClassID)).ToDictionary(static group => group.Key, static group => (uint) group.Sum(static item => item.Amount));
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(3, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void MultipleSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void MaxItemsTooSmall() {
const uint appID = 42;
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 }
};
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1);
[TestMethod]
public void MultipleSetsDifferentAmount() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, 2),
CreateCard(2, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void MutliRarityAndType() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
// for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 },
{ (appID, Asset.SteamCommunityContextID, 3), 3 },
{ (appID, Asset.SteamCommunityContextID, 4), 1 },
{ (appID, Asset.SteamCommunityContextID, 7), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void NotAllCardsPresent() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OneSet() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherAppIDFullSets() {
const uint appID0 = 42;
const uint appID1 = 43;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(1, appID1)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 1 },
{ appID1, 1 }
}
);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID0, Asset.SteamCommunityContextID, 1), 1 },
{ (appID1, Asset.SteamCommunityContextID, 1), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherAppIDNoSets() {
const uint appID0 = 42;
const uint appID1 = 43;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(1, appID1)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 2 },
{ appID1, 2 }
}
);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherAppIDOneSet() {
const uint appID0 = 42;
const uint appID1 = 43;
const uint appID2 = 44;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(1, appID1),
CreateCard(2, appID1),
CreateCard(3, appID1)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 3 },
{ appID1, 3 },
{ appID2, 3 }
}
);
Assert.Fail();
}
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID1, Asset.SteamCommunityContextID, 1), 1 },
{ (appID1, Asset.SteamCommunityContextID, 2), 1 },
{ (appID1, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityFullSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityNoSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityOneSet() {
const uint appID = 42;
[TestMethod]
public void MoreCardsThanNeeded() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(2, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
};
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(3, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeFullSets() {
const uint appID = 42;
[TestMethod]
public void MultipleSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(1, appID),
CreateCard(2, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 }
};
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeNoSets() {
const uint appID = 42;
[TestMethod]
public void MultipleSetsDifferentAmount() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> items = new() {
CreateCard(1, appID, 2),
CreateCard(2, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeOneSet() {
const uint appID = 42;
[TestMethod]
public void MutliRarityAndType() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(2, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
// for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
[TestMethod]
public void TooHighAmount() {
const uint appID0 = 42;
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 },
{ (appID, Asset.SteamCommunityContextID, 2), 2 },
{ (appID, Asset.SteamCommunityContextID, 3), 3 },
{ (appID, Asset.SteamCommunityContextID, 4), 1 },
{ (appID, Asset.SteamCommunityContextID, 7), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void NotAllCardsPresent() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
HashSet<Asset> items = new() {
CreateCard(1, appID0, 2),
CreateCard(2, appID0)
};
[TestMethod]
public void OneSet() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID),
CreateCard(2, appID)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 }
};
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID0, Asset.SteamCommunityContextID, 1), 1 },
{ (appID0, Asset.SteamCommunityContextID, 2), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherAppIDFullSets() {
const uint appID0 = 42;
const uint appID1 = 43;
[TestMethod]
public void TooManyCardsForSingleTrade() {
const uint appID = 42;
HashSet<Asset> items = new();
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
items.Add(CreateCard(1, appID));
items.Add(CreateCard(2, appID));
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(1, appID1)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 1 },
{ appID1, 1 }
}
);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID0, Asset.SteamCommunityContextID, 1), 1 },
{ (appID1, Asset.SteamCommunityContextID, 1), 1 }
};
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
}
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void TooManyCardsForSingleTradeMultipleAppIDs() {
const uint appID0 = 42;
const uint appID1 = 43;
[TestMethod]
public void OtherAppIDNoSets() {
const uint appID0 = 42;
const uint appID1 = 43;
HashSet<Asset> items = new();
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(1, appID1)
};
for (byte i = 0; i < 100; i++) {
items.Add(CreateCard(1, appID0));
items.Add(CreateCard(2, appID0));
items.Add(CreateCard(1, appID1));
items.Add(CreateCard(2, appID1));
}
Dictionary<uint, byte> itemsPerSet = new() {
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 2 },
{ appID1, 2 }
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void TooManyCardsPerSet() {
const uint appID0 = 42;
const uint appID1 = 43;
const uint appID2 = 44;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(3, appID0),
CreateCard(4, appID0)
};
GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 3 },
{ appID1, 3 },
{ appID2, 3 }
}
);
Assert.Fail();
}
private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection<Asset> itemsToSend) {
if (expectedResult == null) {
throw new ArgumentNullException(nameof(expectedResult));
}
);
if (itemsToSend == null) {
throw new ArgumentNullException(nameof(itemsToSend));
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherAppIDOneSet() {
const uint appID0 = 42;
const uint appID1 = 43;
const uint appID2 = 44;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(1, appID1),
CreateCard(2, appID1),
CreateCard(3, appID1)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 3 },
{ appID1, 3 },
{ appID2, 3 }
}
);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), long> realResult = itemsToSend.GroupBy(asset => (asset.RealAppID, asset.ContextID, asset.ClassID)).ToDictionary(group => group.Key, group => group.Sum(asset => asset.Amount));
Assert.AreEqual(expectedResult.Count, realResult.Count);
Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality)));
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID1, Asset.SteamCommunityContextID, 1), 1 },
{ (appID1, Asset.SteamCommunityContextID, 2), 1 },
{ (appID1, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityFullSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityNoSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherRarityOneSet() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, rarity: Asset.ERarity.Common),
CreateCard(2, appID, rarity: Asset.ERarity.Common),
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeFullSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 2 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeNoSets() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new(0);
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void OtherTypeOneSet() {
const uint appID = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID, type: Asset.EType.TradingCard),
CreateCard(2, appID, type: Asset.EType.TradingCard),
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID, Asset.SteamCommunityContextID, 1), 1 },
{ (appID, Asset.SteamCommunityContextID, 2), 1 },
{ (appID, Asset.SteamCommunityContextID, 3), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void TooHighAmount() {
const uint appID0 = 42;
HashSet<Asset> items = new() {
CreateCard(1, appID0, 2),
CreateCard(2, appID0)
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = new() {
{ (appID0, Asset.SteamCommunityContextID, 1), 1 },
{ (appID0, Asset.SteamCommunityContextID, 2), 1 }
};
AssertResultMatchesExpectation(expectedResult, itemsToSend);
}
[TestMethod]
public void TooManyCardsForSingleTrade() {
const uint appID = 42;
HashSet<Asset> items = new();
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
items.Add(CreateCard(1, appID));
items.Add(CreateCard(2, appID));
}
private static Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
}
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) {
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
[TestMethod]
public void TooManyCardsForSingleTradeMultipleAppIDs() {
const uint appID0 = 42;
const uint appID1 = 43;
return GetItemsForFullSets(inventory, inventorySets.ToDictionary(kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet();
HashSet<Asset> items = new();
for (byte i = 0; i < 100; i++) {
items.Add(CreateCard(1, appID0));
items.Add(CreateCard(2, appID0));
items.Add(CreateCard(1, appID1));
items.Add(CreateCard(2, appID1));
}
Dictionary<uint, byte> itemsPerSet = new() {
{ appID0, 2 },
{ appID1, 2 }
};
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
Assert.IsTrue(itemsToSend.Count <= Steam.Exchange.Trading.MaxItemsPerTrade);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void TooManyCardsPerSet() {
const uint appID0 = 42;
const uint appID1 = 43;
const uint appID2 = 44;
HashSet<Asset> items = new() {
CreateCard(1, appID0),
CreateCard(2, appID0),
CreateCard(3, appID0),
CreateCard(4, appID0)
};
GetItemsForFullBadge(
items, new Dictionary<uint, byte> {
{ appID0, 3 },
{ appID1, 3 },
{ appID2, 3 }
}
);
Assert.Fail();
}
private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection<Asset> itemsToSend) {
ArgumentNullException.ThrowIfNull(expectedResult);
ArgumentNullException.ThrowIfNull(itemsToSend);
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), long> realResult = itemsToSend.GroupBy(static asset => (asset.RealAppID, asset.ContextID, asset.ClassID)).ToDictionary(static group => group.Key, static group => group.Sum(static asset => asset.Amount));
Assert.AreEqual(expectedResult.Count, realResult.Count);
Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality)));
}
private static Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) {
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
return GetItemsForFullSets(inventory, inventorySets.ToDictionary(static kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet();
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,179 +27,180 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static ArchiSteamFarm.Steam.Integration.SteamChatMessage;
namespace ArchiSteamFarm.Tests {
[TestClass]
public sealed class SteamChatMessage {
[TestMethod]
public async Task CanSplitEvenWithStupidlyLongPrefix() {
string prefix = new('x', MaxMessagePrefixBytes);
namespace ArchiSteamFarm.Tests;
const string emoji = "😎";
const string message = emoji + emoji + emoji + emoji;
[TestClass]
public sealed class SteamChatMessage {
[TestMethod]
public async Task CanSplitEvenWithStupidlyLongPrefix() {
string prefix = new('x', MaxMessagePrefixBytes);
List<string> output = await GetMessageParts(message, prefix, true).ToListAsync().ConfigureAwait(false);
const string emoji = "😎";
const string message = $"{emoji}{emoji}{emoji}{emoji}";
Assert.AreEqual(4, output.Count);
List<string> output = await GetMessageParts(message, prefix, true).ToListAsync().ConfigureAwait(false);
Assert.AreEqual(prefix + emoji + ContinuationCharacter, output[0]);
Assert.AreEqual(prefix + ContinuationCharacter + emoji + ContinuationCharacter, output[1]);
Assert.AreEqual(prefix + ContinuationCharacter + emoji + ContinuationCharacter, output[2]);
Assert.AreEqual(prefix + ContinuationCharacter + emoji, output[3]);
}
Assert.AreEqual(4, output.Count);
[TestMethod]
public void ContinuationCharacterSizeIsProperlyCalculated() => Assert.AreEqual(ContinuationCharacterBytes, Encoding.UTF8.GetByteCount(ContinuationCharacter.ToString()));
Assert.AreEqual($"{prefix}{emoji}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}{ContinuationCharacter}", output[1]);
Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}{ContinuationCharacter}", output[2]);
Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}", output[3]);
}
[TestMethod]
public async Task DoesntSkipEmptyNewlines() {
string message = "asdf" + Environment.NewLine + Environment.NewLine + "asdf";
[TestMethod]
public void ContinuationCharacterSizeIsProperlyCalculated() => Assert.AreEqual(ContinuationCharacterBytes, Encoding.UTF8.GetByteCount(ContinuationCharacter.ToString()));
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task DoesntSkipEmptyNewlines() {
string message = $"asdf{Environment.NewLine}{Environment.NewLine}asdf";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitInTheMiddleOfMultiByteChar(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
const string emoji = "😎";
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitInTheMiddleOfMultiByteChar(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
string longSequence = new('a', longLineLength - 1);
string message = longSequence + emoji;
const string emoji = "😎";
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
string longSequence = new('a', longLineLength - 1);
string message = $"{longSequence}{emoji}";
Assert.AreEqual(2, output.Count);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
Assert.AreEqual(longSequence + ContinuationCharacter, output[0]);
Assert.AreEqual(ContinuationCharacter + emoji, output[1]);
}
Assert.AreEqual(2, output.Count);
[TestMethod]
public async Task DoesntSplitJustBecauseOfLastEscapableCharacter() {
const string message = "abcdef[";
const string escapedMessage = @"abcdef\[";
Assert.AreEqual($"{longSequence}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{ContinuationCharacter}{emoji}", output[1]);
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task DoesntSplitJustBecauseOfLastEscapableCharacter() {
const string message = "abcdef[";
const string escapedMessage = @"abcdef\[";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(escapedMessage, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitOnBackslashNotUsedForEscaping(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
Assert.AreEqual(1, output.Count);
Assert.AreEqual(escapedMessage, output.First());
}
string longLine = new('a', longLineLength - 2);
string message = longLine + @"\";
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitOnBackslashNotUsedForEscaping(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
string longLine = new('a', longLineLength - 2);
string message = $@"{longLine}\";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message + @"\", output.First());
}
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitOnEscapeCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
Assert.AreEqual(1, output.Count);
Assert.AreEqual($@"{message}\", output.First());
}
string longLine = new('a', longLineLength - 1);
string message = longLine + "[";
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task DoesntSplitOnEscapeCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
string longLine = new('a', longLineLength - 1);
string message = $"{longLine}[";
Assert.AreEqual(2, output.Count);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
Assert.AreEqual(longLine + ContinuationCharacter, output[0]);
Assert.AreEqual(ContinuationCharacter + @"\[", output[1]);
}
Assert.AreEqual(2, output.Count);
[TestMethod]
public async Task NoNeedForAnySplittingWithNewlines() {
string message = "abcdef" + Environment.NewLine + "ghijkl" + Environment.NewLine + "mnopqr";
Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
Assert.AreEqual($@"{ContinuationCharacter}\[", output[1]);
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task NoNeedForAnySplittingWithNewlines() {
string message = $"abcdef{Environment.NewLine}ghijkl{Environment.NewLine}mnopqr";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task NoNeedForAnySplittingWithoutNewlines() {
const string message = "abcdef";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task NoNeedForAnySplittingWithoutNewlines() {
const string message = "abcdef";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public void ParagraphCharacterSizeIsLessOrEqualToContinuationCharacterSize() => Assert.IsTrue(ContinuationCharacterBytes >= Encoding.UTF8.GetByteCount(ParagraphCharacter.ToString()));
Assert.AreEqual(1, output.Count);
Assert.AreEqual(message, output.First());
}
[TestMethod]
public async Task ProperlyEscapesCharacters() {
const string message = @"[b]bold[/b] \n";
const string escapedMessage = @"\[b]bold\[/b] \\n";
[TestMethod]
public void ParagraphCharacterSizeIsLessOrEqualToContinuationCharacterSize() => Assert.IsTrue(ContinuationCharacterBytes >= Encoding.UTF8.GetByteCount(ParagraphCharacter.ToString()));
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task ProperlyEscapesCharacters() {
const string message = @"[b]bold[/b] \n";
const string escapedMessage = @"\[b]bold\[/b] \\n";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(escapedMessage, output.First());
}
List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
[TestMethod]
public async Task ProperlyEscapesSteamMessagePrefix() {
const string prefix = "/pre []";
const string escapedPrefix = @"/pre \[]";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(escapedMessage, output.First());
}
const string message = "asdf";
[TestMethod]
public async Task ProperlyEscapesSteamMessagePrefix() {
const string prefix = "/pre []";
const string escapedPrefix = @"/pre \[]";
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
const string message = "asdf";
Assert.AreEqual(1, output.Count);
Assert.AreEqual(escapedPrefix + message, output.First());
}
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task ProperlySplitsLongSingleLine(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
Assert.AreEqual(1, output.Count);
Assert.AreEqual($"{escapedPrefix}{message}", output.First());
}
string longLine = new('a', longLineLength);
string message = longLine + longLine + longLine + longLine;
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task ProperlySplitsLongSingleLine(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
string longLine = new('a', longLineLength);
string message = $"{longLine}{longLine}{longLine}{longLine}";
Assert.AreEqual(4, output.Count);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
Assert.AreEqual(longLine + ContinuationCharacter, output[0]);
Assert.AreEqual(ContinuationCharacter + longLine + ContinuationCharacter, output[1]);
Assert.AreEqual(ContinuationCharacter + longLine + ContinuationCharacter, output[2]);
Assert.AreEqual(ContinuationCharacter + longLine, output[3]);
}
Assert.AreEqual(4, output.Count);
[TestMethod]
public void ReservedSizeForEscapingIsProperlyCalculated() => Assert.AreEqual(ReservedEscapeMessageBytes, Encoding.UTF8.GetByteCount(@"\") + 4); // Maximum amount of bytes per single UTF-8 character is 4, not 6 as from Encoding.UTF8.GetMaxByteCount(1)
Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
Assert.AreEqual($"{ContinuationCharacter}{longLine}{ContinuationCharacter}", output[1]);
Assert.AreEqual($"{ContinuationCharacter}{longLine}{ContinuationCharacter}", output[2]);
Assert.AreEqual($"{ContinuationCharacter}{longLine}", output[3]);
}
[TestMethod]
public async Task RyzhehvostInitialTestForSplitting() {
const string prefix = "/me ";
[TestMethod]
public void ReservedSizeForEscapingIsProperlyCalculated() => Assert.AreEqual(ReservedEscapeMessageBytes, Encoding.UTF8.GetByteCount(@"\") + 4); // Maximum amount of bytes per single UTF-8 character is 4, not 6 as from Encoding.UTF8.GetMaxByteCount(1)
const string message = @"<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
[TestMethod]
public async Task RyzhehvostInitialTestForSplitting() {
const string prefix = "/me ";
const string message = @"<XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
<XLimited5> Уже имеет: app/349520 | Armillo
<XLimited5> Уже имеет: app/346330 | BrainBread 2
<XLimited5> Уже имеет: app/1086690 | C-War 2
@@ -254,82 +255,81 @@ namespace ArchiSteamFarm.Tests {
<ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
<ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.";
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
Assert.AreEqual(2, output.Count);
Assert.AreEqual(2, output.Count);
foreach (string messagePart in output) {
if ((messagePart.Length <= prefix.Length) || !messagePart.StartsWith(prefix, StringComparison.Ordinal)) {
Assert.Fail();
foreach (string messagePart in output) {
if ((messagePart.Length <= prefix.Length) || !messagePart.StartsWith(prefix, StringComparison.Ordinal)) {
Assert.Fail();
return;
}
string[] lines = messagePart.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
int bytes = lines.Where(line => line.Length > 0).Sum(Encoding.UTF8.GetByteCount) + ((lines.Length - 1) * NewlineWeight);
if (bytes > MaxMessageBytesForUnlimitedAccounts) {
Assert.Fail();
return;
}
}
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task SplitsOnNewlinesWithParagraphCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
StringBuilder newlinePartBuilder = new();
for (ushort bytes = 0; bytes < maxMessageBytes - ReservedContinuationMessageBytes - NewlineWeight;) {
if (newlinePartBuilder.Length > 0) {
bytes += NewlineWeight;
newlinePartBuilder.Append(Environment.NewLine);
}
bytes++;
newlinePartBuilder.Append('a');
return;
}
string newlinePart = newlinePartBuilder.ToString();
string message = newlinePart + Environment.NewLine + newlinePart + Environment.NewLine + newlinePart + Environment.NewLine + newlinePart;
string[] lines = messagePart.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
int bytes = lines.Where(static line => line.Length > 0).Sum(Encoding.UTF8.GetByteCount) + ((lines.Length - 1) * NewlineWeight);
Assert.AreEqual(4, output.Count);
if (bytes > MaxMessageBytesForUnlimitedAccounts) {
Assert.Fail();
Assert.AreEqual(newlinePart + ParagraphCharacter, output[0]);
Assert.AreEqual(newlinePart + ParagraphCharacter, output[1]);
Assert.AreEqual(newlinePart + ParagraphCharacter, output[2]);
Assert.AreEqual(newlinePart, output[3]);
}
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[TestMethod]
public async Task ThrowsOnTooLongNewlinesPrefix() {
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
const string message = "asdf";
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
Assert.Fail();
}
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[TestMethod]
public async Task ThrowsOnTooLongPrefix() {
string prefix = new('x', MaxMessagePrefixBytes + 1);
const string message = "asdf";
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
Assert.Fail();
return;
}
}
}
[DataRow(false)]
[DataRow(true)]
[DataTestMethod]
public async Task SplitsOnNewlinesWithParagraphCharacter(bool isAccountLimited) {
int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
StringBuilder newlinePartBuilder = new();
for (ushort bytes = 0; bytes < maxMessageBytes - ReservedContinuationMessageBytes - NewlineWeight;) {
if (newlinePartBuilder.Length > 0) {
bytes += NewlineWeight;
newlinePartBuilder.Append(Environment.NewLine);
}
bytes++;
newlinePartBuilder.Append('a');
}
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);
Assert.AreEqual(4, output.Count);
Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[0]);
Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[1]);
Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[2]);
Assert.AreEqual(newlinePart, output[3]);
}
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[TestMethod]
public async Task ThrowsOnTooLongNewlinesPrefix() {
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
const string message = "asdf";
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
Assert.Fail();
}
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[TestMethod]
public async Task ThrowsOnTooLongPrefix() {
string prefix = new('x', MaxMessagePrefixBytes + 1);
const string message = "asdf";
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
Assert.Fail();
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,369 +24,369 @@ using ArchiSteamFarm.Steam.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static ArchiSteamFarm.Steam.Exchange.Trading;
namespace ArchiSteamFarm.Tests {
[TestClass]
public sealed class Trading {
[TestMethod]
public void MismatchRarityIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, rarity: Asset.ERarity.Rare) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
namespace ArchiSteamFarm.Tests;
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestClass]
public sealed class Trading {
[TestMethod]
public void MismatchRarityIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, rarity: Asset.ERarity.Rare) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
[TestMethod]
public void MismatchRealAppIDsIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, realAppID: 570) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchTypesIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, type: Asset.EType.Emoticon) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameMultiTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, 9, 730, Asset.EType.Emoticon),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameSingleTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, realAppID: 730),
CreateItem(4, realAppID: 730)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameAbrynosWasWrongNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2),
CreateItem(3),
CreateItem(4),
CreateItem(5)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(2)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameDonationAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, type: Asset.EType.SteamGems)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameMultiTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, 9, type: Asset.EType.Emoticon),
CreateItem(4, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(4, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2),
CreateItem(3)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2),
CreateItem(3)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(4, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityBadReject2() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2, 2)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBadWithOverpayingReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2, 2),
CreateItem(3, 2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() {
CreateItem(1),
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBigDifferenceAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 5),
CreateItem(3)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() { CreateItem(3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBigDifferenceReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2),
CreateItem(3, 2),
CreateItem(4, 3),
CreateItem(5, 10)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(2),
CreateItem(5)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(3),
CreateItem(4)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeGoodAccept() {
HashSet<Asset> inventory = new() { CreateItem(1, 2) };
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = new() { CreateItem(1) };
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2, 2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() {
CreateItem(1),
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
private static Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Asset.SteamAppID, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchRealAppIDsIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, realAppID: 570) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MismatchTypesIsNotFair() {
HashSet<Asset> itemsToGive = new() { CreateItem(1, type: Asset.EType.Emoticon) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameMultiTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, 9, 730, Asset.EType.Emoticon),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameSingleTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, realAppID: 730),
CreateItem(4, realAppID: 730)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void MultiGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, realAppID: 730)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, realAppID: 730)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameAbrynosWasWrongNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2),
CreateItem(3),
CreateItem(4),
CreateItem(5)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(2)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameDonationAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, type: Asset.EType.SteamGems)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameMultiTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, 9, type: Asset.EType.Emoticon),
CreateItem(4, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(4, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(3, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameMultiTypeNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 9),
CreateItem(3, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(3, type: Asset.EType.Emoticon)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(2),
CreateItem(4, type: Asset.EType.Emoticon)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2),
CreateItem(3)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2),
CreateItem(3)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(4, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityBadReject2() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2, 2)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameQuantityNeutralAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(1),
CreateItem(2)
};
HashSet<Asset> itemsToReceive = new() { CreateItem(3, 2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBadReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBadWithOverpayingReject() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2, 2),
CreateItem(3, 2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() {
CreateItem(1),
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBigDifferenceAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 5),
CreateItem(3)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() { CreateItem(3) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeBigDifferenceReject() {
HashSet<Asset> inventory = new() {
CreateItem(1),
CreateItem(2, 2),
CreateItem(3, 2),
CreateItem(4, 3),
CreateItem(5, 10)
};
HashSet<Asset> itemsToGive = new() {
CreateItem(2),
CreateItem(5)
};
HashSet<Asset> itemsToReceive = new() {
CreateItem(3),
CreateItem(4)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeGoodAccept() {
HashSet<Asset> inventory = new() { CreateItem(1, 2) };
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeNeutralAccept() {
HashSet<Asset> inventory = new() { CreateItem(1) };
HashSet<Asset> itemsToGive = new() { CreateItem(1) };
HashSet<Asset> itemsToReceive = new() { CreateItem(2) };
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
HashSet<Asset> inventory = new() {
CreateItem(1, 2),
CreateItem(2, 2)
};
HashSet<Asset> itemsToGive = new() { CreateItem(2) };
HashSet<Asset> itemsToReceive = new() {
CreateItem(1),
CreateItem(3)
};
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
}
private static Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Asset.SteamAppID, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
}

View File

@@ -0,0 +1,52 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static ArchiSteamFarm.Core.Utilities;
namespace ArchiSteamFarm.Tests;
[TestClass]
#pragma warning disable CA1724 // We don't care about the potential conflict, as ASF class name has a priority
public sealed class Utilities {
[TestMethod]
public void AdditionallyForbiddenWordsWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("10chars<!>asdf", new HashSet<string> { "chars<!>" }).IsWeak);
[TestMethod]
public void ContextSpecificWordsWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("archisteamfarmpassword").IsWeak);
[TestMethod]
public void LongPassphraseIsNotWeak() => Assert.IsFalse(TestPasswordStrength("10chars<!>asdf").IsWeak);
[TestMethod]
public void RepetitiveCharactersWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("testaaaatest").IsWeak);
[TestMethod]
public void SequentialCharactersWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("testabcdtest").IsWeak);
[TestMethod]
public void SequentialDescendingCharactersWeakenPassphrases() => Assert.IsTrue(TestPasswordStrength("testdcbatest").IsWeak);
[TestMethod]
public void ShortPassphraseIsWeak() => Assert.IsTrue(TestPasswordStrength("four").IsWeak);
}
#pragma warning restore CA1724 // We don't care about the potential conflict, as ASF class name has a priority

View File

@@ -16,6 +16,7 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
DebugFast|Any CPU = DebugFast|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
@@ -24,22 +25,32 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.DebugFast|Any CPU.ActiveCfg = DebugFast|Any CPU
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1501FF07-0114-473F-BDF2-7F8BA34C2948}.Release|Any CPU.Build.0 = Release|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.DebugFast|Any CPU.ActiveCfg = DebugFast|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D552661C-1356-4B82-A019-B83DC260ECF4}.Release|Any CPU.Build.0 = Release|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.DebugFast|Any CPU.ActiveCfg = DebugFast|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE042DA-1152-4F3F-A197-65FD7509E8C6}.Release|Any CPU.Build.0 = Release|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.DebugFast|Any CPU.ActiveCfg = DebugFast|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B8906EC-DF72-4CB8-9ECE-34961C056680}.Release|Any CPU.Build.0 = Release|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.DebugFast|Any CPU.ActiveCfg = DebugFast|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.DebugFast|Any CPU.Build.0 = DebugFast|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{324E42B1-3C5D-421D-A600-1BEEE7AF401A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection

File diff suppressed because one or more lines are too long

View File

@@ -6,20 +6,15 @@
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net48'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp.XPath" />
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
<PackageReference Include="CryptSharpStandard" />
<PackageReference Include="Humanizer" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Nito.AsyncEx.Coordination" />
<PackageReference Include="NLog" />
<PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="SteamKit2" />
<PackageReference Include="Swashbuckle.AspNetCore" />
@@ -27,18 +22,18 @@
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
<PackageReference Include="System.Composition" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="zxcvbn-core" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net48'">
<PackageReference Include="System.IO.FileSystem.AccessControl" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="IndexRange" />
<PackageReference Include="Microsoft.AspNetCore.Cors" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" />
<PackageReference Include="Microsoft.AspNetCore.Localization" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCaching" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" />

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,5 +23,11 @@ using System;
using System.Runtime.CompilerServices;
[assembly: CLSCompliant(false)]
#if ASF_SIGNED_BUILD
[assembly: InternalsVisibleTo("ArchiSteamFarm.Tests, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")]
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper, PublicKey=002400000480000014020000060200000024000052534131001000000100010099f0e5961ec7497fd7de1cba2b8c5eff3b18c1faf3d7a8d56e063359c7f928b54b14eae24d23d9d3c1a5db7ceca82edb6956d43e8ea2a0b7223e6e6836c0b809de43fde69bf33fba73cf669e71449284d477333d4b6e54fb69f7b6c4b4811b8fe26e88975e593cffc0e321490a50500865c01e50ab87c8a943b2a788af47dc20f2b860062b7b6df25477e471a744485a286b435cea2df3953cbb66febd8db73f3ccb4588886373141d200f749ba40bb11926b668cc15f328412dd0b0b835909229985336eb4a34f47925558dc6dc3910ea09c1aad5c744833f26ad9de727559d393526a7a29b3383de87802a034ead8ecc2d37340a5fa9b406774446256337d77e3c9e8486b5e732097e238312deaf5b4efcc04df8ecb986d90ee12b4a8a9a00319cc25cb91fd3e36a3cc39e501f83d14eb1e1a6fa6a1365483d99f4cefad1ea5dec204dad958e2a9a93add19781a8aa7bac71747b11d156711eafd1e873e19836eb573fa5cde284739df09b658ed40c56c7b5a7596840774a7065864e6c2af7b5a8bf7a2d238de83d77891d98ef5a4a58248c655a1c7c97c99e01d9928dc60c629eeb523356dc3686e3f9a1a30ffcd0268cd03718292f21d839fce741f4c1163001ab5b654c37d862998962a05e8028e061c611384772777ef6a49b00ebb4f228308e61b2afe408b33db2d82c4f385e26d7438ec0a183c64eeca4138cbc3dc2")]
#else
[assembly: InternalsVisibleTo("ArchiSteamFarm.Tests")]
[assembly: InternalsVisibleTo("ArchiSteamFarm.OfficialPlugins.SteamTokenDumper")]
#endif

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,30 +23,28 @@ using System;
using System.Collections;
using System.Collections.Generic;
namespace ArchiSteamFarm.Collections {
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
public T Current => Enumerator.Current;
namespace ArchiSteamFarm.Collections;
private readonly IEnumerator<T> Enumerator;
private readonly IDisposable LockObject;
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
public T Current => Enumerator.Current;
object? IEnumerator.Current => Current;
private readonly IEnumerator<T> Enumerator;
private readonly IDisposable LockObject;
internal ConcurrentEnumerator(IReadOnlyCollection<T> collection, IDisposable lockObject) {
if (collection == null) {
throw new ArgumentNullException(nameof(collection));
}
object? IEnumerator.Current => Current;
LockObject = lockObject ?? throw new ArgumentNullException(nameof(lockObject));
Enumerator = collection.GetEnumerator();
}
internal ConcurrentEnumerator(IReadOnlyCollection<T> collection, IDisposable lockObject) {
ArgumentNullException.ThrowIfNull(collection);
public void Dispose() {
Enumerator.Dispose();
LockObject.Dispose();
}
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
LockObject = lockObject ?? throw new ArgumentNullException(nameof(lockObject));
Enumerator = collection.GetEnumerator();
}
public void Dispose() {
Enumerator.Dispose();
LockObject.Dispose();
}
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,180 +26,174 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Collections {
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where T : notnull {
public event EventHandler? OnModified;
namespace ArchiSteamFarm.Collections;
public int Count => BackingCollection.Count;
public bool IsReadOnly => false;
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where T : notnull {
public event EventHandler? OnModified;
private readonly ConcurrentDictionary<T, bool> BackingCollection;
public int Count => BackingCollection.Count;
public bool IsReadOnly => false;
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
private readonly ConcurrentDictionary<T, bool> BackingCollection;
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
if (comparer == null) {
throw new ArgumentNullException(nameof(comparer));
}
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
BackingCollection = new ConcurrentDictionary<T, bool>(comparer);
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
ArgumentNullException.ThrowIfNull(comparer);
BackingCollection = new ConcurrentDictionary<T, bool>(comparer);
}
public bool Add(T item) {
if (!BackingCollection.TryAdd(item, true)) {
return false;
}
public bool Add(T item) {
if (!BackingCollection.TryAdd(item, true)) {
return false;
}
OnModified?.Invoke(this, EventArgs.Empty);
OnModified?.Invoke(this, EventArgs.Empty);
return true;
}
return true;
public void Clear() {
if (BackingCollection.IsEmpty) {
return;
}
public void Clear() {
if (BackingCollection.IsEmpty) {
return;
}
BackingCollection.Clear();
BackingCollection.Clear();
OnModified?.Invoke(this, EventArgs.Empty);
}
OnModified?.Invoke(this, EventArgs.Empty);
}
public bool Contains(T item) => BackingCollection.ContainsKey(item);
public bool Contains(T item) => BackingCollection.ContainsKey(item);
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
public void ExceptWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
public void ExceptWith(IEnumerable<T> other) {
if (other == null) {
throw new ArgumentNullException(nameof(other));
}
foreach (T item in other) {
Remove(item);
}
}
public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
public void IntersectWith(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
Remove(item);
}
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
}
public bool IsSubsetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return this.All(otherSet.Contains);
}
public bool IsSupersetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return otherSet.All(Contains);
}
public bool Overlaps(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return otherSet.Any(Contains);
}
public bool Remove(T item) {
if (!BackingCollection.TryRemove(item, out _)) {
return false;
}
OnModified?.Invoke(this, EventArgs.Empty);
return true;
}
public bool SetEquals(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count == Count) && otherSet.All(Contains);
}
public void SymmetricExceptWith(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
HashSet<T> removed = new();
foreach (T item in otherSet.Where(Contains)) {
removed.Add(item);
Remove(item);
}
foreach (T item in otherSet.Where(item => !removed.Contains(item))) {
Add(item);
}
}
public void UnionWith(IEnumerable<T> other) {
if (other == null) {
throw new ArgumentNullException(nameof(other));
}
foreach (T otherElement in other) {
Add(otherElement);
}
}
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
[PublicAPI]
public bool AddRange(IEnumerable<T> items) {
bool result = false;
foreach (T _ in items.Where(Add)) {
result = true;
}
return result;
}
[PublicAPI]
public bool RemoveRange(IEnumerable<T> items) {
bool result = false;
foreach (T _ in items.Where(Remove)) {
result = true;
}
return result;
}
[PublicAPI]
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
if (SetEquals(other)) {
return false;
}
ReplaceWith(other);
return true;
}
[PublicAPI]
public void ReplaceWith(IEnumerable<T> other) {
Clear();
UnionWith(other);
foreach (T item in other) {
Remove(item);
}
}
public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
public void IntersectWith(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
Remove(item);
}
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
}
public bool IsSubsetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return this.All(otherSet.Contains);
}
public bool IsSupersetOf(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return otherSet.All(Contains);
}
public bool Overlaps(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return otherSet.Any(Contains);
}
public bool Remove(T item) {
if (!BackingCollection.TryRemove(item, out _)) {
return false;
}
OnModified?.Invoke(this, EventArgs.Empty);
return true;
}
public bool SetEquals(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
return (otherSet.Count == Count) && otherSet.All(Contains);
}
public void SymmetricExceptWith(IEnumerable<T> other) {
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
HashSet<T> removed = new();
foreach (T item in otherSet.Where(Contains)) {
removed.Add(item);
Remove(item);
}
foreach (T item in otherSet.Where(item => !removed.Contains(item))) {
Add(item);
}
}
public void UnionWith(IEnumerable<T> other) {
ArgumentNullException.ThrowIfNull(other);
foreach (T otherElement in other) {
Add(otherElement);
}
}
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
[PublicAPI]
public bool AddRange(IEnumerable<T> items) {
bool result = false;
foreach (T _ in items.Where(Add)) {
result = true;
}
return result;
}
[PublicAPI]
public bool RemoveRange(IEnumerable<T> items) {
bool result = false;
foreach (T _ in items.Where(Remove)) {
result = true;
}
return result;
}
[PublicAPI]
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
if (SetEquals(other)) {
return false;
}
ReplaceWith(other);
return true;
}
[PublicAPI]
public void ReplaceWith(IEnumerable<T> other) {
Clear();
UnionWith(other);
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,95 +23,95 @@ using System.Collections;
using System.Collections.Generic;
using Nito.AsyncEx;
namespace ArchiSteamFarm.Collections {
internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
public bool IsReadOnly => false;
namespace ArchiSteamFarm.Collections;
internal int Count {
get {
using (Lock.ReaderLock()) {
return BackingCollection.Count;
}
}
}
internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
public bool IsReadOnly => false;
private readonly List<T> BackingCollection = new();
private readonly AsyncReaderWriterLock Lock = new();
int ICollection<T>.Count => Count;
int IReadOnlyCollection<T>.Count => Count;
public T this[int index] {
get {
using (Lock.ReaderLock()) {
return BackingCollection[index];
}
}
set {
using (Lock.WriterLock()) {
BackingCollection[index] = value;
}
}
}
public void Add(T item) {
using (Lock.WriterLock()) {
BackingCollection.Add(item);
}
}
public void Clear() {
using (Lock.WriterLock()) {
BackingCollection.Clear();
}
}
public bool Contains(T item) {
internal int Count {
get {
using (Lock.ReaderLock()) {
return BackingCollection.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex) {
using (Lock.ReaderLock()) {
BackingCollection.CopyTo(array, arrayIndex);
}
}
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
public int IndexOf(T item) {
using (Lock.ReaderLock()) {
return BackingCollection.IndexOf(item);
}
}
public void Insert(int index, T item) {
using (Lock.WriterLock()) {
BackingCollection.Insert(index, item);
}
}
public bool Remove(T item) {
using (Lock.WriterLock()) {
return BackingCollection.Remove(item);
}
}
public void RemoveAt(int index) {
using (Lock.WriterLock()) {
BackingCollection.RemoveAt(index);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith(IEnumerable<T> collection) {
using (Lock.WriterLock()) {
BackingCollection.Clear();
BackingCollection.AddRange(collection);
return BackingCollection.Count;
}
}
}
private readonly List<T> BackingCollection = new();
private readonly AsyncReaderWriterLock Lock = new();
int ICollection<T>.Count => Count;
int IReadOnlyCollection<T>.Count => Count;
public T this[int index] {
get {
using (Lock.ReaderLock()) {
return BackingCollection[index];
}
}
set {
using (Lock.WriterLock()) {
BackingCollection[index] = value;
}
}
}
public void Add(T item) {
using (Lock.WriterLock()) {
BackingCollection.Add(item);
}
}
public void Clear() {
using (Lock.WriterLock()) {
BackingCollection.Clear();
}
}
public bool Contains(T item) {
using (Lock.ReaderLock()) {
return BackingCollection.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex) {
using (Lock.ReaderLock()) {
BackingCollection.CopyTo(array, arrayIndex);
}
}
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
public int IndexOf(T item) {
using (Lock.ReaderLock()) {
return BackingCollection.IndexOf(item);
}
}
public void Insert(int index, T item) {
using (Lock.WriterLock()) {
BackingCollection.Insert(index, item);
}
}
public bool Remove(T item) {
using (Lock.WriterLock()) {
return BackingCollection.Remove(item);
}
}
public void RemoveAt(int index) {
using (Lock.WriterLock()) {
BackingCollection.RemoveAt(index);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith(IEnumerable<T> collection) {
using (Lock.WriterLock()) {
BackingCollection.Clear();
BackingCollection.AddRange(collection);
}
}
}

View File

@@ -4,7 +4,7 @@
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Łukasz "JustArchi" Domeradzki
// Copyright 2015-2022 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,53 +25,53 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using ArchiSteamFarm.Core;
namespace ArchiSteamFarm.Collections {
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
private readonly ConcurrentQueue<T> BackingQueue = new();
namespace ArchiSteamFarm.Collections;
internal byte MaxCount {
get => BackingMaxCount;
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
private readonly ConcurrentQueue<T> BackingQueue = new();
set {
if (value == 0) {
ASF.ArchiLogger.LogNullError(nameof(value));
internal byte MaxCount {
get => BackingMaxCount;
return;
}
set {
if (value == 0) {
ASF.ArchiLogger.LogNullError(nameof(value));
BackingMaxCount = value;
Resize();
}
}
private byte BackingMaxCount;
internal FixedSizeConcurrentQueue(byte maxCount) {
if (maxCount == 0) {
throw new ArgumentNullException(nameof(maxCount));
}
MaxCount = maxCount;
}
public IEnumerator<T> GetEnumerator() => BackingQueue.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void Enqueue(T obj) {
BackingQueue.Enqueue(obj);
Resize();
}
private void Resize() {
if (BackingQueue.Count <= MaxCount) {
return;
}
lock (BackingQueue) {
while ((BackingQueue.Count > MaxCount) && BackingQueue.TryDequeue(out _)) { }
}
BackingMaxCount = value;
Resize();
}
}
private byte BackingMaxCount;
internal FixedSizeConcurrentQueue(byte maxCount) {
if (maxCount == 0) {
throw new ArgumentNullException(nameof(maxCount));
}
MaxCount = maxCount;
}
public IEnumerator<T> GetEnumerator() => BackingQueue.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void Enqueue(T obj) {
BackingQueue.Enqueue(obj);
Resize();
}
private void Resize() {
if (BackingQueue.Count <= MaxCount) {
return;
}
lock (BackingQueue) {
while ((BackingQueue.Count > MaxCount) && BackingQueue.TryDequeue(out _)) { }
}
}
}

View File

@@ -1,74 +0,0 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Ł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.Threading.Tasks;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Compatibility {
[PublicAPI]
public static class File {
public static Task AppendAllTextAsync(string path, string contents) {
#if NETFRAMEWORK
System.IO.File.AppendAllText(path, contents);
return Task.CompletedTask;
#else
return System.IO.File.AppendAllTextAsync(path, contents);
#endif
}
public static void Move(string sourceFileName, string destFileName, bool overwrite) {
#if NETFRAMEWORK
if (overwrite && System.IO.File.Exists(destFileName)) {
System.IO.File.Delete(destFileName);
}
System.IO.File.Move(sourceFileName, destFileName);
#else
System.IO.File.Move(sourceFileName, destFileName, overwrite);
#endif
}
public static Task<byte[]> ReadAllBytesAsync(string path) =>
#if NETFRAMEWORK
Task.FromResult(System.IO.File.ReadAllBytes(path));
#else
System.IO.File.ReadAllBytesAsync(path);
#endif
public static Task<string> ReadAllTextAsync(string path) =>
#if NETFRAMEWORK
Task.FromResult(System.IO.File.ReadAllText(path));
#else
System.IO.File.ReadAllTextAsync(path);
#endif
public static Task WriteAllTextAsync(string path, string contents) {
#if NETFRAMEWORK
System.IO.File.WriteAllText(path, contents);
return Task.CompletedTask;
#else
return System.IO.File.WriteAllTextAsync(path, contents);
#endif
}
}
}

View File

@@ -1,129 +0,0 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Ł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.
#if NETFRAMEWORK
using System;
using System.Text;
#endif
using JetBrains.Annotations;
namespace ArchiSteamFarm.Compatibility {
[PublicAPI]
public static class Path {
public static string GetRelativePath(string relativeTo, string path) {
#if NETFRAMEWORK
if (string.IsNullOrEmpty(relativeTo)) {
throw new ArgumentNullException(nameof(relativeTo));
}
if (string.IsNullOrEmpty(path)) {
throw new ArgumentNullException(nameof(path));
}
StringComparison comparisonType = PathInternalNetCore.StringComparison;
relativeTo = System.IO.Path.GetFullPath(relativeTo);
path = System.IO.Path.GetFullPath(path);
// Need to check if the roots are different- if they are we need to return the "to" path.
if (!PathInternalNetCore.AreRootsEqual(relativeTo, path, comparisonType)) {
return path;
}
int commonLength = PathInternalNetCore.GetCommonPathLength(
relativeTo, path,
comparisonType == StringComparison.OrdinalIgnoreCase
);
// If there is nothing in common they can't share the same root, return the "to" path as is.
if (commonLength == 0) {
return path;
}
// Trailing separators aren't significant for comparison
int relativeToLength = relativeTo.Length;
if (PathInternalNetCore.EndsInDirectorySeparator(relativeTo)) {
relativeToLength--;
}
bool pathEndsInSeparator = PathInternalNetCore.EndsInDirectorySeparator(path);
int pathLength = path.Length;
if (pathEndsInSeparator) {
pathLength--;
}
// If we have effectively the same path, return "."
if ((relativeToLength == pathLength) && (commonLength >= relativeToLength)) {
return ".";
}
// We have the same root, we need to calculate the difference now using the
// common Length and Segment count past the length.
//
// Some examples:
//
// C:\Foo C:\Bar L3, S1 -> ..\Bar
// C:\Foo C:\Foo\Bar L6, S0 -> Bar
// C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
// C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
StringBuilder sb = new(); //StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
// Add parent segments for segments past the common on the "from" path
if (commonLength < relativeToLength) {
sb.Append("..");
for (int i = commonLength + 1; i < relativeToLength; i++) {
if (PathInternalNetCore.IsDirectorySeparator(relativeTo[i])) {
sb.Append(System.IO.Path.DirectorySeparatorChar);
sb.Append("..");
}
}
} else if (PathInternalNetCore.IsDirectorySeparator(path[commonLength])) {
// No parent segments and we need to eat the initial separator
// (C:\Foo C:\Foo\Bar case)
commonLength++;
}
// Now add the rest of the "to" path, adding back the trailing separator
int differenceLength = pathLength - commonLength;
if (pathEndsInSeparator) {
differenceLength++;
}
if (differenceLength > 0) {
if (sb.Length > 0) {
sb.Append(System.IO.Path.DirectorySeparatorChar);
}
sb.Append(path, commonLength, differenceLength);
}
return sb.ToString(); //StringBuilderCache.GetStringAndRelease(sb);
#else
return System.IO.Path.GetRelativePath(relativeTo, path);
#endif
}
}
}

View File

@@ -1,173 +0,0 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Ł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.
#if NETFRAMEWORK
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ArchiSteamFarm.Compatibility {
internal static class PathInternalNetCore {
private const string ExtendedDevicePathPrefix = @"\\?\";
private const string UncExtendedPathPrefix = @"\\?\UNC\";
internal static StringComparison StringComparison => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
/// <summary>
/// Returns true if the two paths have the same root
/// </summary>
internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) {
int firstRootLength = GetRootLength(first);
int secondRootLength = GetRootLength(second);
return (firstRootLength == secondRootLength)
&& (string.Compare(
first,
0,
second,
0,
firstRootLength,
comparisonType
) == 0);
}
/// <summary>
/// Returns true if the path ends in a directory separator.
/// </summary>
internal static bool EndsInDirectorySeparator(string path) => (path.Length > 0) && IsDirectorySeparator(path[^1]);
/// <summary>
/// Get the common path length from the start of the string.
/// </summary>
internal static int GetCommonPathLength(string first, string second, bool ignoreCase) {
int commonChars = EqualStartingCharacterCount(first, second, ignoreCase);
// If nothing matches
if (commonChars == 0) {
return commonChars;
}
// Or we're a full string and equal length or match to a separator
if ((commonChars == first.Length)
&& ((commonChars == second.Length) || IsDirectorySeparator(second[commonChars]))) {
return commonChars;
}
if ((commonChars == second.Length) && IsDirectorySeparator(first[commonChars])) {
return commonChars;
}
// It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
while ((commonChars > 0) && !IsDirectorySeparator(first[commonChars - 1])) {
commonChars--;
}
return commonChars;
}
/// <summary>
/// True if the given character is a directory separator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c) => (c == System.IO.Path.DirectorySeparatorChar) || (c == System.IO.Path.AltDirectorySeparatorChar);
/// <summary>
/// Gets the count of common characters from the left optionally ignoring case
/// </summary>
private static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) {
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) {
return 0;
}
int commonChars = 0;
fixed (char* f = first)
fixed (char* s = second) {
char* l = f;
char* r = s;
char* leftEnd = l + first.Length;
char* rightEnd = r + second.Length;
while ((l != leftEnd) && (r != rightEnd)
&& ((*l == *r) || (ignoreCase &&
(char.ToUpperInvariant(*l) == char.ToUpperInvariant(*r))))) {
commonChars++;
l++;
r++;
}
}
return commonChars;
}
/// <summary>
/// Gets the length of the root of the path (drive, share, etc.).
/// </summary>
private static int GetRootLength(string path) {
int i = 0;
int volumeSeparatorLength = 2; // Length to the colon "C:"
int uncRootLength = 2; // Length to the start of the server name "\\"
bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix, StringComparison.Ordinal);
bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix, StringComparison.Ordinal);
if (extendedSyntax) {
// Shift the position we look for the root from to account for the extended prefix
if (extendedUncSyntax) {
// "\\" -> "\\?\UNC\"
uncRootLength = UncExtendedPathPrefix.Length;
} else {
// "C:" -> "\\?\C:"
volumeSeparatorLength += ExtendedDevicePathPrefix.Length;
}
}
if ((!extendedSyntax || extendedUncSyntax) && (path.Length > 0) && IsDirectorySeparator(path[0])) {
// UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
i = 1; // Drive rooted (\foo) is one character
if (extendedUncSyntax || ((path.Length > 1) && IsDirectorySeparator(path[1]))) {
// UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
// (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
i = uncRootLength;
int n = 2; // Maximum separators to skip
while ((i < path.Length) && (!IsDirectorySeparator(path[i]) || (--n > 0))) {
i++;
}
}
} else if ((path.Length >= volumeSeparatorLength) &&
(path[volumeSeparatorLength - 1] == System.IO.Path.VolumeSeparatorChar)) {
// Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
// If the colon is followed by a directory separator, move past it
i = volumeSeparatorLength;
if ((path.Length >= volumeSeparatorLength + 1) && IsDirectorySeparator(path[volumeSeparatorLength])) {
i++;
}
}
return i;
}
}
}
#endif

View File

@@ -1,207 +0,0 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2021 Ł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.
#if NETFRAMEWORK
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.WebSockets;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Hosting;
#endif
using System;
using System.Diagnostics;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Compatibility {
[PublicAPI]
public static class StaticHelpers {
#if NETFRAMEWORK
private static readonly DateTime SavedProcessStartTime = DateTime.UtcNow;
#endif
#if NETFRAMEWORK
public static bool IsRunningOnMono => Type.GetType("Mono.Runtime") != null;
#else
public static bool IsRunningOnMono => false;
#endif
public static DateTime ProcessStartTime {
get {
#if NETFRAMEWORK
if (IsRunningOnMono) {
return SavedProcessStartTime;
}
#endif
using Process process = Process.GetCurrentProcess();
return process.StartTime;
}
}
#if NETFRAMEWORK
public static Task<byte[]> ComputeHashAsync(this HashAlgorithm hashAlgorithm, Stream inputStream) {
if (hashAlgorithm == null) {
throw new ArgumentNullException(nameof(hashAlgorithm));
}
return Task.FromResult(hashAlgorithm.ComputeHash(inputStream));
}
public static IAsyncDisposable ConfigureAwait(this IDisposable source, bool _) {
if (source == null) {
throw new ArgumentNullException(nameof(source));
}
return new AsyncDisposableWrapper(source);
}
public static IWebHostBuilder ConfigureWebHostDefaults(this IWebHostBuilder builder, Action<IWebHostBuilder> configure) {
if (configure == null) {
throw new ArgumentNullException(nameof(configure));
}
configure(builder);
return builder;
}
public static bool Contains(this string input, string value, StringComparison comparisonType) {
if (input == null) {
throw new ArgumentNullException(nameof(input));
}
return input.IndexOf(value, comparisonType) >= 0;
}
// ReSharper disable once UseDeconstructionOnParameter - we actually implement deconstruction here
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kv, out TKey key, out TValue value) {
key = kv.Key;
value = kv.Value;
}
public static ValueTask DisposeAsync(this IDisposable disposable) {
if (disposable == null) {
throw new ArgumentNullException(nameof(disposable));
}
disposable.Dispose();
return default(ValueTask);
}
public static int IndexOf(this string source, char value, StringComparison comparisonType) {
if (source == null) {
throw new ArgumentNullException(nameof(source));
}
return source.IndexOf(value.ToString(), comparisonType);
}
public static async Task<int> ReadAsync(this Stream stream, ReadOnlyMemory<byte> buffer) {
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
byte[] byteArray = buffer.ToArray();
return await stream.ReadAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);
}
public static async Task<WebSocketReceiveResult> ReceiveAsync(this WebSocket webSocket, byte[] buffer, CancellationToken cancellationToken) {
if (webSocket == null) {
throw new ArgumentNullException(nameof(webSocket));
}
return await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(false);
}
public static string Replace(this string source, string oldValue, string? newValue, StringComparison comparisonType) {
if (source == null) {
throw new ArgumentNullException(nameof(source));
}
if (oldValue == null) {
throw new ArgumentNullException(nameof(oldValue));
}
if (oldValue.Length == 0) {
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(oldValue)), nameof(oldValue));
}
int startIndex = 0;
while (true) {
if (startIndex >= source.Length) {
return source;
}
int index = source.IndexOf(oldValue, startIndex, comparisonType);
if (index < 0) {
return source;
}
startIndex = index;
source = source.Remove(index, oldValue.Length);
if (!string.IsNullOrEmpty(newValue)) {
source = source.Insert(index, newValue!);
startIndex += newValue!.Length;
}
}
}
public static async Task SendAsync(this WebSocket webSocket, byte[] buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) {
if (webSocket == null) {
throw new ArgumentNullException(nameof(webSocket));
}
await webSocket.SendAsync(new ArraySegment<byte>(buffer), messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
}
public static string[] Split(this string text, char separator, StringSplitOptions options = StringSplitOptions.None) {
if (text == null) {
throw new ArgumentNullException(nameof(text));
}
return text.Split(new[] { separator }, options);
}
public static void TrimExcess<TKey, TValue>(this Dictionary<TKey, TValue> _) { } // no-op
public static async Task WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer) {
if (stream == null) {
throw new ArgumentNullException(nameof(stream));
}
byte[] byteArray = buffer.ToArray();
await stream.WriteAsync(byteArray, 0, byteArray.Length).ConfigureAwait(false);
}
#endif
}
}

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