Compare commits

..

100 Commits

Author SHA1 Message Date
JustArchi
a76fe94617 Translations update 2019-07-07 12:10:39 +02:00
JustArchi
a60c659cd4 Closes #1316 2019-07-07 11:39:44 +02:00
JustArchi
697952eded Closes #1314 2019-07-06 17:56:11 +02:00
Vitaliy
d240f6595d Fix circular reference exception from Kestrel (#1313) 2019-07-06 14:34:48 +02:00
JustArchi
fc23a426a2 Misc 2019-07-05 11:43:20 +02:00
JustArchi
d70e71dd68 Closes #1312 2019-07-05 11:39:19 +02:00
dependabot-preview[bot]
f2089118cf Bump ASF-WebConfigGenerator from 45722bc to aac8d80
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `45722bc` to `aac8d80`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](45722bc75a...aac8d80712)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 01:42:12 +00:00
dependabot-preview[bot]
c9a065c118 Bump ASF-ui from 09a98a9 to 75132d8
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `09a98a9` to `75132d8`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](09a98a97ed...75132d888a)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 01:34:24 +00:00
dependabot-preview[bot]
6968913e76 Bump wiki from 770d5f9 to 6651953
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `770d5f9` to `6651953`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](770d5f9bdf...665195342e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-05 01:27:09 +00:00
dependabot-preview[bot]
b8690eb8e8 Bump Markdig.Signed from 0.17.0 to 0.17.1
Bumps [Markdig.Signed](https://github.com/lunet-io/markdig) from 0.17.0 to 0.17.1.
- [Release notes](https://github.com/lunet-io/markdig/releases)
- [Changelog](https://github.com/lunet-io/markdig/blob/master/changelog.md)
- [Commits](https://github.com/lunet-io/markdig/compare/0.17.0...0.17.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-04 20:56:44 +00:00
JustArchi
4aebae6132 Bump 2019-07-04 16:08:49 +02:00
JustArchi
06d2680438 Enhance addlicense in a similar way
Thanks for idea @ryzhehvost
2019-07-04 15:44:53 +02:00
JustArchi
ac53ed5653 Bump 2019-07-04 12:28:06 +02:00
JustArchi
d828eaa074 Translations update 2019-07-04 12:24:01 +02:00
dependabot-preview[bot]
20fccaba16 Bump JetBrains.Annotations from 2019.1.1 to 2019.1.3
Bumps JetBrains.Annotations from 2019.1.1 to 2019.1.3.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-03 20:51:09 +00:00
Łukasz Domeradzki
a1330cf31e Misc 2019-07-03 13:35:19 +02:00
dependabot-preview[bot]
bf642b61ea Bump wiki from 2e6688f to 5e44368
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `2e6688f` to `5e44368`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](2e6688ff27...5e443681c9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-03 01:35:04 +00:00
dependabot-preview[bot]
ad116cbeac Bump ASF-ui from 2b650ef to bfef082
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `2b650ef` to `bfef082`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](2b650efe20...bfef082760)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-03 01:27:14 +00:00
JustArchi
dbffb536d2 Misc 2019-07-03 01:03:10 +02:00
JustArchi
3955cd96d1 Make owns types case-insensitive 2019-07-03 00:54:12 +02:00
dependabot-preview[bot]
689f8564b8 Bump NLog.Web.AspNetCore from 4.8.3 to 4.8.4
Bumps [NLog.Web.AspNetCore](https://github.com/NLog/NLog.Web) from 4.8.3 to 4.8.4.
- [Release notes](https://github.com/NLog/NLog.Web/releases)
- [Changelog](https://github.com/NLog/NLog.Web/blob/dev/CHANGELOG.MD)
- [Commits](https://github.com/NLog/NLog.Web/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-02 21:50:28 +00:00
JustArchi
4ec4eaa8ab Make use of cache for gameName as well 2019-07-02 22:04:28 +02:00
JustArchi
86de831062 Misc 2019-07-02 21:50:18 +02:00
JustArchi
db1d5256af How many mistakes I can do in this function? 2019-07-02 21:40:37 +02:00
JustArchi
7f64190100 Misc 2019-07-02 21:30:55 +02:00
JustArchi
8b1cc1030e Include game name in the output stats 2019-07-02 21:22:28 +02:00
JustArchi
e947bac0e7 Final touches 2019-07-02 21:17:27 +02:00
JustArchi
96061f14c5 Misc 2019-07-02 20:51:18 +02:00
JustArchi
0d7a9c27e5 Misc 2019-07-02 15:28:37 +02:00
JustArchi
b30e3f3b5d Improve owns command to take into account owned packageIDs
Idea thanks to @ryzhehvost
2019-07-02 15:06:01 +02:00
dependabot-preview[bot]
495684dfee Bump ASF-WebConfigGenerator from 9bd6bc3 to 15116bc
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `9bd6bc3` to `15116bc`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](9bd6bc3081...15116bc4d5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-02 01:34:01 +00:00
dependabot-preview[bot]
49287ef857 Bump ASF-ui from 5efb068 to 2b650ef
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `5efb068` to `2b650ef`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](5efb068f72...2b650efe20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-02 01:26:28 +00:00
JustArchi
fc9f849439 Closes #1300 2019-07-01 16:56:41 +02:00
JustArchi
32e0de5a70 Implement basic logic for #1299 2019-06-30 23:33:50 +02:00
JustArchi
4749a1785f Bump 2019-06-30 22:06:07 +02:00
JustArchi
c012d90ff6 Translations update 2019-06-30 13:37:01 +02:00
JustArchi
11ab6fdda1 Translations update 2019-06-29 16:10:08 +02:00
JustArchi
4592a20c89 General code review 2019-06-29 15:59:38 +02:00
dependabot-preview[bot]
c69bad6fec Bump SteamKit2 from 2.2.0-Beta.3 to 2.2.0
Bumps [SteamKit2](https://github.com/SteamRE/SteamKit) from 2.2.0-Beta.3 to 2.2.0.
- [Release notes](https://github.com/SteamRE/SteamKit/releases)
- [Commits](https://github.com/SteamRE/SteamKit/compare/2.2.0-Beta.3...2.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-28 01:26:44 +00:00
dependabot-preview[bot]
da8e7c7b94 Bump Microsoft.NET.Test.Sdk from 16.1.1 to 16.2.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.1.1 to 16.2.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-27 06:48:39 +00:00
dependabot-preview[bot]
30df2811bf Bump HtmlAgilityPack from 1.11.7 to 1.11.8
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.7 to 1.11.8.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.7...v1.11.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-26 14:21:15 +00:00
dependabot-preview[bot]
6bcb458eb8 Bump ASF-ui from af3b48f to fb20056
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `af3b48f` to `fb20056`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](af3b48f523...fb20056940)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-26 01:34:30 +00:00
dependabot-preview[bot]
7f1e323ff0 Bump ASF-WebConfigGenerator from 51c6962 to 9bd6bc3
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `51c6962` to `9bd6bc3`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](51c6962c3b...9bd6bc3081)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-26 01:26:47 +00:00
JustArchi
b5fab46f5f Translations update 2019-06-25 14:15:29 +02:00
JustArchi
514599390f Misc 2019-06-25 12:37:22 +02:00
JustArchi
448482e499 Use modded identifier for plugins setups 2019-06-25 12:26:55 +02:00
dependabot-preview[bot]
73a991afa8 Bump ASF-ui from 63ad325 to e4bbf29
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `63ad325` to `e4bbf29`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](63ad32564f...e4bbf299b0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-25 01:27:16 +00:00
JustArchi
f76393890c Update bitcoin address to bech32 2019-06-25 00:13:50 +02:00
dependabot-preview[bot]
8315e8f69d Bump wiki from 43865ec to 7bd5f70
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `43865ec` to `7bd5f70`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](43865ec5de...7bd5f70046)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-24 01:26:30 +00:00
JustArchi
22c1b6130a Closes #1290 2019-06-23 17:30:34 +02:00
JustArchi
a37569dfb2 Bump 2019-06-23 02:07:53 +02:00
JustArchi
08926aa2f1 Improve InvalidPassword/RateLimitExceeded procedure
Previously we had this awful "assume rate limit if invalid password" because Steam used to do this falsely for rate limits as well.

Since I can no longer reproduce this false behaviour with latest network, I assume that Valve corrected whatever they had broken back then and the network will properly tell us RLE in RLE condition, and invalid password when account credentials are invalid.

There is still case for invalid account credentials when login key is invalid, and we should still handle that one gracefully.
2019-06-23 02:05:49 +02:00
JustArchi
b2eabe93d9 Bump 2019-06-23 01:21:50 +02:00
JustArchi
bbd9ab89d3 Translations update 2019-06-23 01:18:50 +02:00
JustArchi
4050e6eb60 Closes #1289 2019-06-23 01:06:11 +02:00
JustArchi
4824386e88 Bump 2019-06-20 21:18:20 +02:00
JustArchi
82501413a9 Translations update 2019-06-20 20:58:28 +02:00
JustArchi
64b379f4a2 Misc 2019-06-20 13:52:19 +02:00
dependabot-preview[bot]
82fb45bd0e Bump wiki from 63f3d5b to 006f811
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `63f3d5b` to `006f811`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](63f3d5b9d7...006f811c16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-20 01:28:26 +00:00
JustArchi
6082ee8644 Misc 2019-06-19 18:52:30 +02:00
JustArchi
e94162ae1f Relax requirements for MatchActively 2019-06-19 18:50:26 +02:00
JustArchi
4faabe2429 Omit RFC for Steam compat 2019-06-19 16:42:04 +02:00
JustArchi
24732a6f61 Misc 2019-06-19 14:14:35 +02:00
dependabot-preview[bot]
7d87d0f4a6 Bump ASF-ui from 48d557d to cbc060d
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `48d557d` to `cbc060d`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](48d557d6da...cbc060d9da)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-19 01:36:01 +00:00
dependabot-preview[bot]
7f406eb0a7 Bump wiki from 6e1c629 to 63f3d5b
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `6e1c629` to `63f3d5b`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](6e1c62952d...63f3d5b9d7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-19 01:27:06 +00:00
JustArchi
f4a21ce20c Misc 2019-06-18 14:48:06 +02:00
JustArchi
c1a1fef7d8 Bump 2019-06-16 16:29:43 +02:00
JustArchi
3a9fa2456b Closes #1284 2019-06-16 16:25:01 +02:00
JustArchi
e482295b13 Translations update 2019-06-16 11:22:01 +02:00
JustArchi
58691180d0 Misc 2019-06-15 17:24:18 +02:00
Łukasz Domeradzki
3a836c228c Update SUPPORT.md 2019-06-14 13:57:40 +02:00
JustArchi
8dd75d5313 Avoid ASF crash in #1283
Won't solve the misconfiguration caused by the user, but ASF crash can be avoided at this stage.
2019-06-14 13:53:28 +02:00
dependabot-preview[bot]
f9b5e40fb3 Bump wiki from 8079331 to 31d17bd
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `8079331` to `31d17bd`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](80793314de...31d17bd8c6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-14 01:54:16 +00:00
dependabot-preview[bot]
f6fb1a27a5 Bump ASF-WebConfigGenerator from b3acf4a to 70b34be
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `b3acf4a` to `70b34be`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](b3acf4ac65...70b34becd1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-14 01:43:00 +00:00
dependabot-preview[bot]
34a5999711 Bump NLog from 4.6.4 to 4.6.5
Bumps [NLog](https://github.com/NLog/NLog) from 4.6.4 to 4.6.5.
- [Release notes](https://github.com/NLog/NLog/releases)
- [Changelog](https://github.com/NLog/NLog/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/NLog/NLog/compare/v4.6.4...v4.6.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-14 01:34:37 +00:00
dependabot-preview[bot]
e219c1eac1 Bump ASF-ui from eca8a8b to cc10a98
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `eca8a8b` to `cc10a98`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](eca8a8b060...cc10a98563)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-14 01:27:41 +00:00
dependabot-preview[bot]
a93262aa9d Bump ConfigureAwaitChecker.Analyzer from 3.0.0 to 4.0.0
Bumps [ConfigureAwaitChecker.Analyzer](https://github.com/cincuranet/ConfigureAwaitChecker) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/cincuranet/ConfigureAwaitChecker/releases)
- [Commits](https://github.com/cincuranet/ConfigureAwaitChecker/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-13 16:11:36 +00:00
Łukasz Domeradzki
740cc5e97c Update Bug_report.md 2019-06-13 18:05:55 +02:00
Łukasz Domeradzki
2532d96ca6 Update SUPPORT.md 2019-06-13 17:59:22 +02:00
Łukasz Domeradzki
6b4550b86b Create SUPPORT.md 2019-06-13 17:51:40 +02:00
Łukasz Domeradzki
b7d12053b1 Create SECURITY.md 2019-06-13 17:36:26 +02:00
JustArchi
fefd764325 Closes #1277 2019-06-13 16:31:39 +02:00
dependabot-preview[bot]
c7733e1dd9 Bump wiki from 4edde12 to 8079331
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `4edde12` to `8079331`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](4edde121ec...80793314de)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-13 01:34:14 +00:00
dependabot-preview[bot]
f3ae19f4af Bump ASF-WebConfigGenerator from 31bdcaf to b3acf4a
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `31bdcaf` to `b3acf4a`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](31bdcaf91b...b3acf4ac65)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-13 01:27:29 +00:00
dependabot-preview[bot]
72862d8ff0 Bump ASF-ui from f9645c0 to eca8a8b
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `f9645c0` to `eca8a8b`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](f9645c02b9...eca8a8b060)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-12 01:26:02 +00:00
dependabot-preview[bot]
3633a67661 Bump ASF-ui from 3a3632c to f9645c0
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `3a3632c` to `f9645c0`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](3a3632cd67...f9645c02b9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-11 01:33:57 +00:00
dependabot-preview[bot]
7a0a6c6b6f Bump wiki from 5d1c70e to 4edde12
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `5d1c70e` to `4edde12`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](5d1c70e07c...4edde121ec)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-11 01:27:16 +00:00
dependabot-preview[bot]
88e22bfe77 Bump ASF-ui from 4f020cf to 3a3632c
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `4f020cf` to `3a3632c`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](4f020cf02e...3a3632cd67)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-10 01:27:13 +00:00
dependabot-preview[bot]
73f88a069d Bump ASF-ui from c461455 to 4f020cf
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `c461455` to `4f020cf`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](c4614559e0...4f020cf02e)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 01:33:28 +00:00
dependabot-preview[bot]
6eb9b888f1 Bump ASF-WebConfigGenerator from 675251e to 31bdcaf
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `675251e` to `31bdcaf`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](675251e997...31bdcaf91b)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-07 01:26:29 +00:00
dependabot-preview[bot]
8a7c22b9d5 Bump ASF-ui from f823d05 to c461455
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `f823d05` to `c461455`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](f823d05ec6...c4614559e0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-06 01:57:26 +00:00
dependabot-preview[bot]
c6168413b4 Bump wiki from b65ec70 to 5d1c70e
Bumps [wiki](https://github.com/JustArchiNET/ArchiSteamFarm.wiki) from `b65ec70` to `5d1c70e`.
- [Release notes](https://github.com/JustArchiNET/ArchiSteamFarm.wiki/releases)
- [Commits](b65ec70ac9...5d1c70e07c)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-06 01:42:08 +00:00
dependabot-preview[bot]
b1f7912a21 Bump ASF-WebConfigGenerator from da31d27 to 675251e
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `da31d27` to `675251e`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](da31d274a4...675251e997)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-06 01:34:50 +00:00
dependabot-preview[bot]
f985159fe4 Bump NLog.Web.AspNetCore from 4.8.2 to 4.8.3
Bumps [NLog.Web.AspNetCore](https://github.com/NLog/NLog.Web) from 4.8.2 to 4.8.3.
- [Release notes](https://github.com/NLog/NLog.Web/releases)
- [Changelog](https://github.com/NLog/NLog.Web/blob/dev/CHANGELOG.MD)
- [Commits](https://github.com/NLog/NLog.Web/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-06 01:28:24 +00:00
dependabot-preview[bot]
bab57b95ef Bump ASF-ui from f48de0c to f823d05
Bumps [ASF-ui](https://github.com/JustArchiNET/ASF-ui) from `f48de0c` to `f823d05`.
- [Release notes](https://github.com/JustArchiNET/ASF-ui/releases)
- [Commits](f48de0c250...f823d05ec6)
2019-06-04 01:34:25 +00:00
dependabot-preview[bot]
903fb677c0 Bump ASF-WebConfigGenerator from 85eac6c to da31d27
Bumps [ASF-WebConfigGenerator](https://github.com/JustArchiNET/ASF-WebConfigGenerator) from `85eac6c` to `da31d27`.
- [Release notes](https://github.com/JustArchiNET/ASF-WebConfigGenerator/releases)
- [Commits](85eac6c8d1...da31d274a4)
2019-06-04 01:26:56 +00:00
dependabot-preview[bot]
8a03a53629 Bump tools/ArchiCrowdin from 6695f74 to 8aeda69
Bumps [tools/ArchiCrowdin](https://github.com/JustArchiNET/ArchiCrowdin) from `6695f74` to `8aeda69`.
- [Release notes](https://github.com/JustArchiNET/ArchiCrowdin/releases)
- [Commits](6695f74e12...8aeda69935)
2019-06-03 01:33:04 +00:00
JustArchi
42e5a99225 Misc
No need to fiddle with Linux console anyway, since it's UTF-8 already (and we can't change it even if it isn't)
2019-06-02 14:39:16 +02:00
JustArchi
da16086119 Misc 2019-06-02 14:29:26 +02:00
JustArchi
cc6df1082e Bump 2019-06-02 00:31:31 +02:00
40 changed files with 853 additions and 428 deletions

View File

@@ -43,7 +43,7 @@ Feel free to remove our notice and fill the template below with your details.
```
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 the wiki. Standard ASF log does not include sensitive information.
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)

2
ASF-ui

Submodule ASF-ui updated: f48de0c250...0c3584253d

View File

@@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Composition.AttributedModel" Version="*" />

View File

@@ -153,8 +153,8 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// 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 or a web browser
// Once all plugins execute their OnLoaded() methods, OnASFInit() will be called next
// 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!");

View File

@@ -34,10 +34,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>

View File

@@ -92,14 +92,14 @@ namespace ArchiSteamFarm {
}
internal static async Task Init() {
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
await UpdateAndRestart().ConfigureAwait(false);
if (!PluginsCore.InitPlugins()) {
await Task.Delay(10000).ConfigureAwait(false);
}
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
await UpdateAndRestart().ConfigureAwait(false);
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
StringComparer botsComparer = await PluginsCore.GetBotsComparer().ConfigureAwait(false);
@@ -346,12 +346,7 @@ namespace ArchiSteamFarm {
return false;
}
switch (botName) {
case SharedInfo.ASF:
return false;
default:
return true;
}
return !botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase);
}
private static async void OnChanged(object sender, FileSystemEventArgs e) {
@@ -426,7 +421,7 @@ namespace ArchiSteamFarm {
return;
}
if (botName.Equals(SharedInfo.ASF)) {
if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) {
ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged);
await RestartOrExit().ConfigureAwait(false);
@@ -520,7 +515,7 @@ namespace ArchiSteamFarm {
return;
}
if (botName.Equals(SharedInfo.ASF)) {
if (botName.Equals(SharedInfo.ASF, StringComparison.OrdinalIgnoreCase)) {
if (File.Exists(fullPath)) {
return;
}

View File

@@ -157,22 +157,42 @@ namespace ArchiSteamFarm {
}
if (resumeInSeconds > 0) {
if (CardsFarmerResumeTimer != null) {
CardsFarmerResumeTimer.Dispose();
CardsFarmerResumeTimer = null;
if (CardsFarmerResumeTimer == null) {
CardsFarmerResumeTimer = new Timer(
e => Resume(),
null,
TimeSpan.FromSeconds(resumeInSeconds), // Delay
Timeout.InfiniteTimeSpan // Period
);
} else {
CardsFarmerResumeTimer.Change(TimeSpan.FromSeconds(resumeInSeconds), Timeout.InfiniteTimeSpan);
}
CardsFarmerResumeTimer = new Timer(
e => Resume(),
null,
TimeSpan.FromSeconds(resumeInSeconds), // Delay
Timeout.InfiniteTimeSpan // Period
);
}
return (true, Strings.BotAutomaticIdlingNowPaused);
}
[PublicAPI]
public async Task<(bool Success, string Message)> Play(IEnumerable<uint> gameIDs, string gameName = null) {
if (gameIDs == null) {
Bot.ArchiLogger.LogNullError(nameof(gameIDs));
return (false, string.Format(Strings.ErrorObjectIsNull, nameof(gameIDs)));
}
if (!Bot.IsConnectedAndLoggedOn) {
return (false, Strings.BotNotConnected);
}
if (!Bot.CardsFarmer.Paused) {
await Bot.CardsFarmer.Pause(true).ConfigureAwait(false);
}
await Bot.ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false);
return (true, Strings.Done);
}
[PublicAPI]
public async Task<ArchiHandler.PurchaseResponseCallback> RedeemKey(string key) {
await LimitGiftsRequestsAsync().ConfigureAwait(false);

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<ApplicationIcon>ASF.ico</ApplicationIcon>
<AssemblyVersion>4.0.2.2</AssemblyVersion>
<AssemblyVersion>4.0.3.0</AssemblyVersion>
<Authors>JustArchi</Authors>
<Company>JustArchi</Company>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
@@ -11,7 +11,7 @@
<DefaultItemExcludes>$(DefaultItemExcludes);config/**;debug/**;out/**;overlay/**</DefaultItemExcludes>
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
<ErrorReport>none</ErrorReport>
<FileVersion>4.0.2.2</FileVersion>
<FileVersion>4.0.3.0</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>1591</NoWarn>
@@ -56,15 +56,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="3.0.0">
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="HtmlAgilityPack" Version="1.11.7" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.8" />
<PackageReference Include="Humanizer" Version="2.6.2" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.1" />
<PackageReference Include="Markdig.Signed" Version="0.17.0" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Markdig.Signed" Version="0.17.1" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
@@ -73,9 +74,9 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview5.19227.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.0.0-preview5.19227.9" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NLog" Version="4.6.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.2" />
<PackageReference Include="SteamKit2" Version="2.2.0-Beta.3" />
<PackageReference Include="NLog" Version="4.6.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />
<PackageReference Include="SteamKit2" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc2" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.0.0-rc2" />
<PackageReference Include="System.Composition" Version="1.3.0-preview5.19224.8" />

View File

@@ -989,6 +989,50 @@ namespace ArchiSteamFarm {
return true;
}
[PublicAPI]
public static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) {
if (string.IsNullOrEmpty(service) || (function == null)) {
ASF.ArchiLogger.LogNullError(nameof(service) + " || " + nameof(function));
return default;
}
if (ASF.GlobalConfig.WebLimiterDelay == 0) {
return await function().ConfigureAwait(false);
}
if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service));
if (!WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
ASF.ArchiLogger.LogNullError(nameof(limiters));
return await function().ConfigureAwait(false);
}
}
// Sending a request opens a new connection
await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false);
try {
// It also increases number of requests
await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
Utilities.InBackground(
async () => {
await Task.Delay(ASF.GlobalConfig.WebLimiterDelay).ConfigureAwait(false);
limiters.RateLimitingSemaphore.Release();
}
);
return await function().ConfigureAwait(false);
} finally {
// We release open connections semaphore only once we're indeed done sending a particular request
limiters.OpenConnectionsSemaphore.Release();
}
}
internal async Task<bool> AcceptDigitalGiftCard(ulong giftCardID) {
if (giftCardID == 0) {
Bot.ArchiLogger.LogNullError(nameof(giftCardID));
@@ -1906,7 +1950,7 @@ namespace ArchiSteamFarm {
}
// Generate login key from the user nonce that we've received from Steam network
byte[] loginKey = Encoding.ASCII.GetBytes(webAPIUserNonce);
byte[] loginKey = Encoding.UTF8.GetBytes(webAPIUserNonce);
// AES encrypt our login key with our session key
byte[] encryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
@@ -1975,6 +2019,12 @@ namespace ArchiSteamFarm {
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost));
// Report proper time when doing timezone-based calculations, see setTimezoneCookies() from https://steamcommunity-a.akamaihd.net/public/shared/javascript/shared_global.js
string timeZoneOffset = DateTimeOffset.Now.Offset.TotalSeconds + WebUtility.UrlEncode(",") + "0";
WebBrowser.CookieContainer.Add(new Cookie("timezoneOffset", timeZoneOffset, "/", "." + SteamCommunityHost));
WebBrowser.CookieContainer.Add(new Cookie("timezoneOffset", timeZoneOffset, "/", "." + SteamStoreHost));
Bot.ArchiLogger.LogGenericInfo(Strings.Success);
// Unlock Steam Parental if needed
@@ -2637,49 +2687,6 @@ namespace ArchiSteamFarm {
return true;
}
private static async Task<T> WebLimitRequest<T>(string service, Func<Task<T>> function) {
if (string.IsNullOrEmpty(service) || (function == null)) {
ASF.ArchiLogger.LogNullError(nameof(service) + " || " + nameof(function));
return default;
}
if (ASF.GlobalConfig.WebLimiterDelay == 0) {
return await function().ConfigureAwait(false);
}
if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service));
if (!WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
ASF.ArchiLogger.LogNullError(nameof(limiters));
return await function().ConfigureAwait(false);
}
}
// Sending a request opens a new connection
await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false);
try {
// It also increases number of requests
await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
// We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation
Utilities.InBackground(
async () => {
await Task.Delay(ASF.GlobalConfig.WebLimiterDelay).ConfigureAwait(false);
limiters.RateLimitingSemaphore.Release();
}
);
return await function().ConfigureAwait(false);
} finally {
// We release open connections semaphore only once we're indeed done sending a particular request
limiters.OpenConnectionsSemaphore.Release();
}
}
public enum ESession : byte {
None,
Lowercase,

View File

@@ -65,8 +65,6 @@ namespace ArchiSteamFarm {
private static readonly SemaphoreSlim LoginRateLimitingSemaphore = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
private static bool LoginRateLimited;
[JsonIgnore]
[PublicAPI]
public readonly Actions Actions;
@@ -86,6 +84,10 @@ namespace ArchiSteamFarm {
[PublicAPI]
public readonly Commands Commands;
[JsonIgnore]
[PublicAPI]
public readonly SteamConfiguration SteamConfiguration;
[JsonProperty]
public uint GamesToRedeemInBackgroundCount => BotDatabase?.GamesToRedeemInBackgroundCount ?? 0;
@@ -103,7 +105,6 @@ namespace ArchiSteamFarm {
internal readonly ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)> OwnedPackageIDs = new ConcurrentDictionary<uint, (EPaymentMethod PaymentMethod, DateTime TimeCreated)>();
internal readonly SteamApps SteamApps;
internal readonly SteamConfiguration SteamConfiguration;
internal readonly SteamFriends SteamFriends;
internal bool CanReceiveSteamCards => !IsAccountLimited && !IsAccountLocked;
@@ -140,9 +141,11 @@ namespace ArchiSteamFarm {
}
}
#pragma warning disable IDE0051
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamID))]
[NotNull]
private string SSteamID => SteamID.ToString();
#pragma warning restore IDE0051
[JsonProperty]
public EAccountFlags AccountFlags { get; private set; }
@@ -170,8 +173,10 @@ namespace ArchiSteamFarm {
private string AuthCode;
#pragma warning disable IDE0052
[JsonProperty]
private string AvatarHash;
#pragma warning restore IDE0052
private Timer ConnectionFailureTimer;
private string DeviceID;
@@ -371,25 +376,27 @@ namespace ArchiSteamFarm {
if (botName.StartsWith("r!", StringComparison.OrdinalIgnoreCase)) {
string botsPattern = botName.Substring(2);
RegexOptions botsRegex = RegexOptions.None;
if ((BotsComparer == StringComparer.InvariantCulture) || (BotsComparer == StringComparer.Ordinal)) {
botsRegex |= RegexOptions.CultureInvariant;
} else if ((BotsComparer == StringComparer.InvariantCultureIgnoreCase) || (BotsComparer == StringComparer.OrdinalIgnoreCase)) {
botsRegex |= RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
}
Regex regex;
try {
RegexOptions botsRegex = RegexOptions.None;
if ((BotsComparer == StringComparer.InvariantCulture) || (BotsComparer == StringComparer.Ordinal)) {
botsRegex |= RegexOptions.CultureInvariant;
} else if ((BotsComparer == StringComparer.InvariantCultureIgnoreCase) || (BotsComparer == StringComparer.OrdinalIgnoreCase)) {
botsRegex |= RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
}
Regex regex = new Regex(botsPattern, botsRegex);
IEnumerable<Bot> regexMatches = Bots.Where(kvp => regex.IsMatch(kvp.Key)).Select(kvp => kvp.Value);
result.UnionWith(regexMatches);
regex = new Regex(botsPattern, botsRegex);
} catch (ArgumentException e) {
ASF.ArchiLogger.LogGenericWarningException(e);
return null;
}
IEnumerable<Bot> regexMatches = Bots.Where(kvp => regex.IsMatch(kvp.Key)).Select(kvp => kvp.Value);
result.UnionWith(regexMatches);
continue;
}
@@ -545,32 +552,30 @@ namespace ArchiSteamFarm {
return (0, DateTime.MaxValue);
}
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID, OwnedPackageIDs.Keys);
if ((packageIDs == null) || (packageIDs.Count == 0)) {
return (0, DateTime.MaxValue);
}
if ((hoursPlayed < CardsFarmer.HoursForRefund) && !BotConfig.IdleRefundableGames) {
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID);
DateTime mostRecent = DateTime.MinValue;
if (packageIDs == null) {
return (0, DateTime.MaxValue);
}
if (packageIDs.Count > 0) {
DateTime mostRecent = DateTime.MinValue;
foreach (uint packageID in packageIDs) {
if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
continue;
}
if (IsRefundable(packageData.PaymentMethod) && (packageData.TimeCreated > mostRecent)) {
mostRecent = packageData.TimeCreated;
}
foreach (uint packageID in packageIDs) {
if (!OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
continue;
}
if (mostRecent > DateTime.MinValue) {
DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund);
if (IsRefundable(packageData.PaymentMethod) && (packageData.TimeCreated > mostRecent)) {
mostRecent = packageData.TimeCreated;
}
}
if (playableIn > DateTime.UtcNow) {
return (0, playableIn);
}
if (mostRecent > DateTime.MinValue) {
DateTime playableIn = mostRecent.AddDays(CardsFarmer.DaysForRefund);
if (playableIn > DateTime.UtcNow) {
return (0, playableIn);
}
}
}
@@ -1189,15 +1194,16 @@ namespace ArchiSteamFarm {
EResult result = await ArchiHandler.SendMessage(steamID, messagePart).ConfigureAwait(false);
switch (result) {
case EResult.OK:
sent = true;
break;
case EResult.Fail:
case EResult.RateLimitExceeded:
case EResult.Timeout:
await Task.Delay(5000).ConfigureAwait(false);
continue;
case EResult.OK:
sent = true;
break;
default:
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result), result));
@@ -1257,15 +1263,16 @@ namespace ArchiSteamFarm {
EResult result = await ArchiHandler.SendMessage(chatGroupID, chatID, messagePart).ConfigureAwait(false);
switch (result) {
case EResult.OK:
sent = true;
break;
case EResult.Fail:
case EResult.RateLimitExceeded:
case EResult.Timeout:
await Task.Delay(5000).ConfigureAwait(false);
continue;
case EResult.OK:
sent = true;
break;
default:
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(result), result));
@@ -2031,6 +2038,7 @@ namespace ArchiSteamFarm {
switch (lastLogOnResult) {
case EResult.AccountDisabled:
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
// Do not attempt to reconnect, those failures are permanent
return;
case EResult.Invalid:
@@ -2040,11 +2048,6 @@ namespace ArchiSteamFarm {
break;
case EResult.InvalidPassword:
// If we didn't use login key, it's nearly always rate limiting
if (string.IsNullOrEmpty(BotDatabase.LoginKey)) {
goto case EResult.RateLimitExceeded;
}
await BotDatabase.SetLoginKey().ConfigureAwait(false);
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
@@ -2059,22 +2062,12 @@ namespace ArchiSteamFarm {
case EResult.RateLimitExceeded:
ArchiLogger.LogGenericInfo(string.Format(Strings.BotRateLimitExceeded, TimeSpan.FromMinutes(LoginCooldownInMinutes).ToHumanReadable()));
if (LoginRateLimited) {
if (!await LoginRateLimitingSemaphore.WaitAsync(1000 * WebBrowser.MaxTries).ConfigureAwait(false)) {
break;
}
await LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (LoginRateLimited) {
break;
}
LoginRateLimited = true;
await Task.Delay(LoginCooldownInMinutes * 60 * 1000).ConfigureAwait(false);
LoginRateLimited = false;
} finally {
LoginRateLimitingSemaphore.Release();
}
@@ -2252,30 +2245,14 @@ namespace ArchiSteamFarm {
return;
}
// Return early if this update doesn't bring anything new
if (callback.LicenseList.Count == OwnedPackageIDs.Count) {
if (callback.LicenseList.All(license => OwnedPackageIDs.ContainsKey(license.PackageID))) {
if (!await CardsFarmer.Resume(false).ConfigureAwait(false)) {
await ResetGamesPlayed().ConfigureAwait(false);
}
return;
}
}
Commands.OnNewLicenseList();
OwnedPackageIDs.Clear();
bool refreshData = !BotConfig.IdleRefundableGames || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesAscending) || BotConfig.FarmingOrders.Contains(BotConfig.EFarmingOrder.RedeemDateTimesDescending);
Dictionary<uint, uint> packagesToRefresh = new Dictionary<uint, uint>();
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList.Where(license => license.PackageID != 0)) {
OwnedPackageIDs[license.PackageID] = (license.PaymentMethod, license.TimeCreated);
if (!refreshData) {
continue;
}
if (!ASF.GlobalDatabase.PackagesData.TryGetValue(license.PackageID, out (uint ChangeNumber, HashSet<uint> _) packageData) || (packageData.ChangeNumber < license.LastChangeNumber)) {
packagesToRefresh[license.PackageID] = (uint) license.LastChangeNumber;
}
@@ -2287,10 +2264,6 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(Strings.Done);
}
if (CardsFarmer.Paused) {
await ResetGamesPlayed().ConfigureAwait(false);
}
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
@@ -2348,6 +2321,7 @@ namespace ArchiSteamFarm {
switch (callback.Result) {
case EResult.AccountDisabled:
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey):
// Those failures are permanent, we should Stop() the bot if any of those happen
ArchiLogger.LogGenericWarning(string.Format(Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
Stop();

View File

@@ -1136,12 +1136,14 @@ namespace ArchiSteamFarm {
foreach (Game game in GamesToFarm) {
DateTime redeemDate = DateTime.MinValue;
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(game.AppID);
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(game.AppID, Bot.OwnedPackageIDs.Keys);
if (packageIDs != null) {
foreach (uint packageID in packageIDs) {
if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
continue;
Bot.ArchiLogger.LogNullError(nameof(packageData));
return;
}
if (packageData.TimeCreated > redeemDate) {

View File

@@ -24,6 +24,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ArchiSteamFarm.Json;
using ArchiSteamFarm.Localization;
@@ -122,6 +123,8 @@ namespace ArchiSteamFarm {
return await ResponsePause(steamID, true).ConfigureAwait(false);
case "PAUSE~":
return await ResponsePause(steamID, false).ConfigureAwait(false);
case "RESET":
return await ResponseReset(steamID).ConfigureAwait(false);
case "RESUME":
return ResponseResume(steamID);
case "RESTART":
@@ -251,6 +254,8 @@ namespace ArchiSteamFarm {
case "R^" when args.Length > 2:
case "REDEEM^" when args.Length > 2:
return await ResponseAdvancedRedeem(steamID, args[1], args[2]).ConfigureAwait(false);
case "RESET":
return await ResponseReset(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "RESUME":
return await ResponseResume(steamID, Utilities.GetArgsAsText(args, 1, ",")).ConfigureAwait(false);
case "START":
@@ -404,6 +409,36 @@ namespace ArchiSteamFarm {
}
}
private async Task<Dictionary<uint, string>> FetchGamesOwned(bool cachedOnly = false) {
lock (CachedGamesOwned) {
if (CachedGamesOwned.Count > 0) {
return new Dictionary<uint, string>(CachedGamesOwned);
}
}
if (cachedOnly) {
return null;
}
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
Dictionary<uint, string> gamesOwned = hasValidApiKey.GetValueOrDefault() ? await Bot.ArchiWebHandler.GetOwnedGames(Bot.SteamID).ConfigureAwait(false) : await Bot.ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false);
if ((gamesOwned != null) && (gamesOwned.Count > 0)) {
lock (CachedGamesOwned) {
if (CachedGamesOwned.Count == 0) {
foreach ((uint appID, string gameName) in gamesOwned) {
CachedGamesOwned[appID] = gameName;
}
CachedGamesOwned.TrimExcess();
}
}
}
return gamesOwned;
}
private async Task<string> Response2FA(ulong steamID) {
if (steamID == 0) {
Bot.ArchiLogger.LogNullError(nameof(steamID));
@@ -486,9 +521,9 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
private async Task<string> ResponseAddLicense(ulong steamID, IReadOnlyCollection<uint> gameIDs) {
if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs));
private async Task<string> ResponseAddLicense(ulong steamID, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
return null;
}
@@ -503,74 +538,73 @@ namespace ArchiSteamFarm {
StringBuilder response = new StringBuilder();
foreach (uint gameID in gameIDs) {
if (await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID)));
string[] entries = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string entry in entries) {
uint gameID;
string type;
int index = entry.IndexOf('/');
if ((index > 0) && (entry.Length > index + 1)) {
if (!uint.TryParse(entry.Substring(index + 1), out gameID) || (gameID == 0)) {
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(gameID))));
continue;
}
type = entry.Substring(0, index);
} else if (uint.TryParse(entry, out gameID) && (gameID > 0)) {
type = "SUB";
} else {
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(gameID))));
continue;
}
SteamApps.FreeLicenseCallback callback;
switch (type.ToUpperInvariant()) {
case "A":
case "APP":
SteamApps.FreeLicenseCallback callback;
try {
callback = await Bot.SteamApps.RequestFreeLicense(gameID);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericWarningException(e);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
try {
callback = await Bot.SteamApps.RequestFreeLicense(gameID);
} catch (Exception e) {
Bot.ArchiLogger.LogGenericWarningException(e);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "app/" + gameID, EResult.Timeout)));
break;
break;
}
if (callback == null) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "app/" + gameID, EResult.Timeout)));
break;
}
response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, "app/" + gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, "app/" + gameID, callback.Result)));
break;
default:
if (!await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, "sub/" + gameID, EResult.Fail)));
continue;
}
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicenseWithItems, gameID, EResult.OK, "sub/" + gameID)));
break;
}
if (callback == null) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
break;
}
response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(Strings.BotAddLicenseWithItems, gameID, callback.Result, string.Join(", ", callback.GrantedApps.Select(appID => "app/" + appID).Union(callback.GrantedPackages.Select(subID => "sub/" + subID)))) : string.Format(Strings.BotAddLicense, gameID, callback.Result)));
}
return response.Length > 0 ? response.ToString() : null;
}
private async Task<string> ResponseAddLicense(ulong steamID, string targetGameIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(targetGameIDs)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetGameIDs));
return null;
}
if (!Bot.HasPermission(steamID, BotConfig.EPermission.Operator)) {
return null;
}
if (!Bot.IsConnectedAndLoggedOn) {
return FormatBotResponse(Strings.BotNotConnected);
}
string[] gameIDs = targetGameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (gameIDs.Length == 0) {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gameIDs)));
}
HashSet<uint> gamesToRedeem = new HashSet<uint>();
foreach (string game in gameIDs) {
if (!uint.TryParse(game, out uint gameID) || (gameID == 0)) {
return FormatBotResponse(string.Format(Strings.ErrorParsingObject, nameof(gameID)));
}
gamesToRedeem.Add(gameID);
}
return await ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
}
[ItemCanBeNull]
private static async Task<string> ResponseAddLicense(ulong steamID, string botNames, string targetGameIDs) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(targetGameIDs)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(targetGameIDs));
private static async Task<string> ResponseAddLicense(ulong steamID, string botNames, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(query)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(query));
return null;
}
@@ -581,7 +615,7 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(steamID, targetGameIDs))).ConfigureAwait(false);
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(steamID, query))).ConfigureAwait(false);
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
@@ -659,34 +693,42 @@ namespace ArchiSteamFarm {
foreach (string flag in flags) {
switch (flag.ToUpperInvariant()) {
case "FD":
case "FORCEDISTRIBUTING":
redeemFlags |= ERedeemFlags.ForceDistributing;
break;
case "FF":
case "FORCEFORWARDING":
redeemFlags |= ERedeemFlags.ForceForwarding;
break;
case "FKMG":
case "FORCEKEEPMISSINGGAMES":
redeemFlags |= ERedeemFlags.ForceKeepMissingGames;
break;
case "SD":
case "SKIPDISTRIBUTING":
redeemFlags |= ERedeemFlags.SkipDistributing;
break;
case "SF":
case "SKIPFORWARDING":
redeemFlags |= ERedeemFlags.SkipForwarding;
break;
case "SI":
case "SKIPINITIAL":
redeemFlags |= ERedeemFlags.SkipInitial;
break;
case "SKMG":
case "SKIPKEEPMISSINGGAMES":
redeemFlags |= ERedeemFlags.SkipKeepMissingGames;
break;
case "V":
case "VALIDATE":
redeemFlags |= ERedeemFlags.Validate;
break;
@@ -1571,7 +1613,8 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
private async Task<(string Response, HashSet<uint> OwnedGameIDs)> ResponseOwns(ulong steamID, string query) {
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
private async Task<(string Response, Dictionary<string, string> OwnedGames)> ResponseOwns(ulong steamID, string query) {
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
@@ -1586,78 +1629,139 @@ namespace ArchiSteamFarm {
return (FormatBotResponse(Strings.BotNotConnected), null);
}
Dictionary<uint, string> ownedGames = null;
lock (CachedGamesOwned) {
if (CachedGamesOwned.Count > 0) {
ownedGames = new Dictionary<uint, string>(CachedGamesOwned);
}
}
if (ownedGames == null) {
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
ownedGames = hasValidApiKey.GetValueOrDefault() ? await Bot.ArchiWebHandler.GetOwnedGames(Bot.SteamID).ConfigureAwait(false) : await Bot.ArchiWebHandler.GetMyOwnedGames().ConfigureAwait(false);
if ((ownedGames == null) || (ownedGames.Count == 0)) {
return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(ownedGames))), null);
}
lock (CachedGamesOwned) {
if (CachedGamesOwned.Count == 0) {
foreach ((uint appID, string gameName) in ownedGames) {
CachedGamesOwned[appID] = gameName;
}
CachedGamesOwned.TrimExcess();
}
}
}
Dictionary<uint, string> gamesOwned = await FetchGamesOwned(true).ConfigureAwait(false);
StringBuilder response = new StringBuilder();
HashSet<uint> ownedGameIDs = new HashSet<uint>();
Dictionary<string, string> result = new Dictionary<string, string>();
if (query.Equals("*")) {
foreach ((uint appID, string gameName) in ownedGames) {
ownedGameIDs.Add(appID);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, appID, gameName)));
}
} else {
string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
string[] entries = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (games.Length == 0) {
return (FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(games))), null);
foreach (string entry in entries) {
string game;
string type;
int index = entry.IndexOf('/');
if ((index > 0) && (entry.Length > index + 1)) {
game = entry.Substring(index + 1);
type = entry.Substring(0, index);
} else if (uint.TryParse(entry, out uint appID) && (appID > 0)) {
game = entry;
type = "APP";
} else {
game = entry;
type = "NAME";
}
foreach (string game in games) {
// Check if this is gameID
if (uint.TryParse(game, out uint gameID) && (gameID != 0)) {
if (Bot.OwnedPackageIDs.ContainsKey(gameID)) {
ownedGameIDs.Add(gameID);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, gameID)));
switch (type.ToUpperInvariant()) {
case "A" when uint.TryParse(game, out uint appID) && (appID > 0):
case "APP" when uint.TryParse(game, out appID) && (appID > 0):
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(appID, Bot.OwnedPackageIDs.Keys);
continue;
if ((packageIDs != null) && (packageIDs.Count > 0)) {
if ((gamesOwned != null) && gamesOwned.TryGetValue(appID, out string cachedGameName)) {
result["app/" + appID] = cachedGameName;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, cachedGameName)));
} else {
result["app/" + appID] = null;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, "app/" + appID)));
}
} else {
if (gamesOwned == null) {
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
if (gamesOwned == null) {
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
break;
}
}
if (gamesOwned.TryGetValue(appID, out string gameName)) {
result["app/" + appID] = gameName;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
} else {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, "app/" + appID)));
}
}
if (ownedGames.TryGetValue(gameID, out string ownedName)) {
ownedGameIDs.Add(gameID);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, gameID, ownedName)));
} else {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, gameID)));
break;
case "R":
case "REGEX":
Regex regex;
try {
regex = new Regex(game);
} catch (ArgumentException e) {
Bot.ArchiLogger.LogGenericWarningException(e);
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsInvalid, nameof(regex))));
break;
}
if (gamesOwned == null) {
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
if (gamesOwned == null) {
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
break;
}
}
bool foundWithRegex = false;
foreach ((uint appID, string gameName) in gamesOwned.Where(gameOwned => regex.IsMatch(gameOwned.Value))) {
foundWithRegex = true;
result["app/" + appID] = gameName;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
}
if (!foundWithRegex) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, entry)));
}
continue;
}
case "S" when uint.TryParse(game, out uint packageID) && (packageID > 0):
case "SUB" when uint.TryParse(game, out packageID) && (packageID > 0):
if (Bot.OwnedPackageIDs.ContainsKey(packageID)) {
result["sub/" + packageID] = null;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlready, "sub/" + packageID)));
} else {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, "sub/" + packageID)));
}
// This is a string, so check our entire library
foreach ((uint appID, string gameName) in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
ownedGameIDs.Add(appID);
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, appID, gameName)));
}
break;
default:
if (gamesOwned == null) {
gamesOwned = await FetchGamesOwned().ConfigureAwait(false);
if (gamesOwned == null) {
response.AppendLine(FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(gamesOwned))));
break;
}
}
bool foundWithName = false;
foreach ((uint appID, string gameName) in gamesOwned.Where(gameOwned => gameOwned.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) {
foundWithName = true;
result["app/" + appID] = gameName;
response.AppendLine(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, "app/" + appID, gameName)));
}
if (!foundWithName) {
response.AppendLine(FormatBotResponse(string.Format(Strings.BotNotOwnedYet, entry)));
}
break;
}
}
return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), ownedGameIDs);
return (response.Length > 0 ? response.ToString() : FormatBotResponse(string.Format(Strings.BotNotOwnedYet, query)), result);
}
[ItemCanBeNull]
@@ -1674,21 +1778,31 @@ namespace ArchiSteamFarm {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IList<(string Response, HashSet<uint> OwnedGameIDs)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(steamID, query))).ConfigureAwait(false);
IList<(string Response, Dictionary<string, string> OwnedGames)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(steamID, query))).ConfigureAwait(false);
List<(string Response, HashSet<uint> OwnedGameIDs)> validResults = new List<(string Response, HashSet<uint> OwnedGameIDs)>(results.Where(result => !string.IsNullOrEmpty(result.Response)));
List<(string Response, Dictionary<string, string> OwnedGames)> validResults = new List<(string Response, Dictionary<string, string> OwnedGames)>(results.Where(result => !string.IsNullOrEmpty(result.Response)));
if (validResults.Count == 0) {
return null;
}
Dictionary<uint, ushort> ownedGameCounts = new Dictionary<uint, ushort>();
Dictionary<string, (ushort Count, string GameName)> ownedGamesStats = new Dictionary<string, (ushort Count, string GameName)>();
foreach (uint gameID in validResults.Where(validResult => (validResult.OwnedGameIDs != null) && (validResult.OwnedGameIDs.Count > 0)).SelectMany(validResult => validResult.OwnedGameIDs)) {
ownedGameCounts[gameID] = ownedGameCounts.TryGetValue(gameID, out ushort count) ? ++count : (ushort) 1;
foreach ((string gameID, string gameName) in validResults.Where(validResult => (validResult.OwnedGames != null) && (validResult.OwnedGames.Count > 0)).SelectMany(validResult => validResult.OwnedGames)) {
if (ownedGamesStats.TryGetValue(gameID, out (ushort Count, string GameName) ownedGameStats)) {
ownedGameStats.Count++;
} else {
ownedGameStats.Count = 1;
}
if (!string.IsNullOrEmpty(gameName)) {
ownedGameStats.GameName = gameName;
}
ownedGamesStats[gameID] = ownedGameStats;
}
IEnumerable<string> extraResponses = ownedGameCounts.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value, validResults.Count, kv.Key)));
IEnumerable<string> extraResponses = ownedGamesStats.Select(kv => FormatStaticResponse(string.Format(Strings.BotOwnsOverviewPerGame, kv.Value.Count, validResults.Count, kv.Key + (!string.IsNullOrEmpty(kv.Value.GameName) ? " | " + kv.Value.GameName : ""))));
return string.Join(Environment.NewLine, validResults.Select(result => result.Response).Concat(extraResponses));
}
@@ -1799,13 +1913,9 @@ namespace ArchiSteamFarm {
return FormatBotResponse(Strings.BotNotConnected);
}
if (!Bot.CardsFarmer.Paused) {
await Bot.CardsFarmer.Pause(false).ConfigureAwait(false);
}
(bool success, string message) = await Bot.Actions.Play(gameIDs, gameName).ConfigureAwait(false);
await Bot.ArchiHandler.PlayGames(gameIDs, gameName).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
return FormatBotResponse(success ? message : string.Format(Strings.WarningFailedWithError, message));
}
private async Task<string> ResponsePlay(ulong steamID, string targetGameIDs) {
@@ -1846,7 +1956,7 @@ namespace ArchiSteamFarm {
gamesToPlay.Add(gameID);
}
return await ResponsePlay(steamID, gamesToPlay, gameName.ToString()).ConfigureAwait(false);
return await ResponsePlay(steamID, gamesToPlay, gameName.Length > 0 ? gameName.ToString() : null).ConfigureAwait(false);
}
[ItemCanBeNull]
@@ -2270,6 +2380,47 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
[ItemCanBeNull]
private static async Task<string> ResponseReset(ulong steamID, string botNames) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames));
return null;
}
HashSet<Bot> bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IList<string> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseReset(steamID))).ConfigureAwait(false);
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
}
private async Task<string> ResponseReset(ulong steamID) {
if (steamID == 0) {
Bot.ArchiLogger.LogNullError(nameof(steamID));
return null;
}
if (!Bot.HasPermission(steamID, BotConfig.EPermission.Master)) {
return null;
}
if (!Bot.IsConnectedAndLoggedOn) {
return FormatBotResponse(Strings.BotNotConnected);
}
(bool success, string message) = await Bot.Actions.Play(Enumerable.Empty<uint>(), Bot.BotConfig.CustomGamePlayedWhileIdle).ConfigureAwait(false);
return FormatBotResponse(success ? message : string.Format(Strings.WarningFailedWithError, message));
}
private static string ResponseRestart(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));

View File

@@ -111,14 +111,24 @@ namespace ArchiSteamFarm {
return globalDatabase;
}
internal HashSet<uint> GetPackageIDs(uint appID) {
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
internal HashSet<uint> GetPackageIDs(uint appID, ICollection<uint> packageIDs) {
if ((appID == 0) || (packageIDs == null) || (packageIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(packageIDs));
return null;
}
return PackagesData.Where(package => package.Value.AppIDs?.Contains(appID) == true).Select(package => package.Key).ToHashSet();
HashSet<uint> result = new HashSet<uint>();
foreach (uint packageID in packageIDs.Where(packageID => packageID != 0)) {
if (!PackagesData.TryGetValue(packageID, out (uint _, HashSet<uint> AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
continue;
}
result.Add(packageID);
}
return result;
}
internal async Task RefreshPackages(Bot bot, IReadOnlyDictionary<uint, uint> packages) {
@@ -140,6 +150,8 @@ namespace ArchiSteamFarm {
Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
if ((packagesData == null) || (packagesData.Count == 0)) {
bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
return;
}

View File

@@ -42,7 +42,7 @@ namespace ArchiSteamFarm.Helpers {
private T InitializedValue;
private Timer MaintenanceTimer;
internal ArchiCacheable([NotNull] Func<Task<(bool Success, T Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
public ArchiCacheable([NotNull] Func<Task<(bool Success, T Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction));
CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan;
}

View File

@@ -100,13 +100,14 @@ namespace ArchiSteamFarm.IPC {
Logging.InitHistoryLogger();
// Start the server
IWebHost kestrelWebHost = builder.Build();
IWebHost kestrelWebHost = null;
try {
kestrelWebHost = builder.Build();
await kestrelWebHost.StartAsync().ConfigureAwait(false);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
kestrelWebHost.Dispose();
kestrelWebHost?.Dispose();
return;
}

View File

@@ -23,6 +23,7 @@ using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
@@ -37,14 +38,19 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
/// This API endpoint is supposed to be entirely replaced by ASF actions available under /Api/ASF/{action} and /Api/Bot/{bot}/{action}.
/// You should use "given bot" commands when executing this endpoint, omitting targets of the command will cause the command to be executed on first defined bot
/// </remarks>
[HttpPost("{command:required}")]
[Consumes("application/json")]
[HttpPost]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> CommandPost(string command) {
if (string.IsNullOrEmpty(command)) {
ASF.ArchiLogger.LogNullError(nameof(command));
public async Task<ActionResult<GenericResponse>> CommandPost([FromBody] CommandRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
}
if (string.IsNullOrEmpty(request.Command)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.Command))));
}
if (ASF.GlobalConfig.SteamOwnerID == 0) {
@@ -57,6 +63,8 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return BadRequest(new GenericResponse(false, Strings.ErrorNoBotsDefined));
}
string command = request.Command;
if (!string.IsNullOrEmpty(ASF.GlobalConfig.CommandPrefix) && command.StartsWith(ASF.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) {
command = command.Substring(ASF.GlobalConfig.CommandPrefix.Length);
@@ -69,5 +77,26 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<string>(response));
}
/// <summary>
/// Executes a command.
/// </summary>
/// <remarks>
/// This API endpoint is supposed to be entirely replaced by ASF actions available under /Api/ASF/{action} and /Api/Bot/{bot}/{action}.
/// You should use "given bot" commands when executing this endpoint, omitting targets of the command will cause the command to be executed on first defined bot
/// </remarks>
[HttpPost("{command:required}")]
[Obsolete]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> ObsoleteCommandPost(string command) {
if (string.IsNullOrEmpty(command)) {
ASF.ArchiLogger.LogNullError(nameof(command));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
}
return await CommandPost(new CommandRequest(command)).ConfigureAwait(false);
}
}
}

View File

@@ -39,20 +39,26 @@ namespace ArchiSteamFarm.IPC.Integration {
private const byte MaxFailedAuthorizationAttempts = 5;
private static readonly SemaphoreSlim AuthorizationSemaphore = new SemaphoreSlim(1, 1);
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private static readonly Timer ClearFailedAuthorizationsTimer = new Timer(
e => FailedAuthorizations.Clear(),
null,
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours), // Delay
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours) // Period
);
private static readonly ConcurrentDictionary<IPAddress, byte> FailedAuthorizations = new ConcurrentDictionary<IPAddress, byte>();
private static Timer ClearFailedAuthorizationsTimer;
private readonly RequestDelegate Next;
public ApiAuthenticationMiddleware([NotNull] RequestDelegate next) => Next = next ?? throw new ArgumentNullException(nameof(next));
public ApiAuthenticationMiddleware([NotNull] RequestDelegate next) {
Next = next ?? throw new ArgumentNullException(nameof(next));
lock (FailedAuthorizations) {
if (ClearFailedAuthorizationsTimer == null) {
ClearFailedAuthorizationsTimer = new Timer(
e => FailedAuthorizations.Clear(),
null,
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours), // Delay
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours) // Period
);
}
}
}
[PublicAPI]
public async Task InvokeAsync(HttpContext context) {

View File

@@ -0,0 +1,49 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2019 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class CommandRequest {
/// <summary>
/// Specifies the command that will be executed by ASF.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string Command;
internal CommandRequest([NotNull] string command) {
if (string.IsNullOrEmpty(command)) {
throw new ArgumentNullException(nameof(command));
}
Command = command;
}
[JsonConstructor]
private CommandRequest() { }
}
}

View File

@@ -29,6 +29,7 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -58,6 +59,9 @@ namespace ArchiSteamFarm.IPC {
app.UsePathBase(pathBase);
}
// Add support for proxies
app.UseForwardedHeaders();
// Add support for response compression
app.UseResponseCompression();
@@ -99,6 +103,9 @@ namespace ArchiSteamFarm.IPC {
// The order of dependency injection matters, pay attention to it
// Add support for proxies
services.Configure<ForwardedHeadersOptions>(options => options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto);
// Add support for response compression
services.AddResponseCompression();

View File

@@ -70,6 +70,7 @@ namespace ArchiSteamFarm.Json {
[PublicAPI]
public EType Type { get; internal set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
[NotNull]
private string AmountText {
@@ -91,6 +92,7 @@ namespace ArchiSteamFarm.Json {
Amount = amount;
}
}
#pragma warning restore IDE0051
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
[NotNull]
@@ -114,6 +116,7 @@ namespace ArchiSteamFarm.Json {
}
}
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[NotNull]
private string ClassIDText {
@@ -133,7 +136,9 @@ namespace ArchiSteamFarm.Json {
ClassID = classID;
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[NotNull]
private string ContextIDText {
@@ -155,13 +160,16 @@ namespace ArchiSteamFarm.Json {
ContextID = contextID;
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
[NotNull]
private string IDText {
get => AssetIDText;
set => AssetIDText = value;
}
#pragma warning restore IDE0051
// Constructed from trades being received or plugins
public Asset(uint appID, ulong contextID, ulong classID, uint amount, bool marketable = true, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
@@ -218,6 +226,7 @@ namespace ArchiSteamFarm.Json {
internal ulong TradeOfferID { get; private set; }
internal EType Type { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "html", Required = Required.DisallowNull)]
private string HTML {
set {
@@ -287,6 +296,7 @@ namespace ArchiSteamFarm.Json {
}
}
}
#pragma warning restore IDE0051
[JsonConstructor]
private ConfirmationDetails() { }
@@ -318,6 +328,7 @@ namespace ArchiSteamFarm.Json {
[PublicAPI]
public bool Success { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "success", Required = Required.Always)]
private byte SuccessNumber {
set {
@@ -337,6 +348,7 @@ namespace ArchiSteamFarm.Json {
}
}
}
#pragma warning restore IDE0051
[JsonConstructor]
protected NumberResponse() { }
@@ -418,6 +430,7 @@ namespace ArchiSteamFarm.Json {
internal ulong LastAssetID { get; private set; }
internal bool MoreItems { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "last_assetid", Required = Required.DisallowNull)]
private string LastAssetIDText {
set {
@@ -436,11 +449,14 @@ namespace ArchiSteamFarm.Json {
LastAssetID = lastAssetID;
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "more_items", Required = Required.DisallowNull)]
private byte MoreItemsNumber {
set => MoreItems = value > 0;
}
#pragma warning restore IDE0051
[JsonConstructor]
private InventoryResponse() { }
@@ -456,6 +472,7 @@ namespace ArchiSteamFarm.Json {
internal bool Tradable { get; private set; }
internal Asset.EType Type { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "classid", Required = Required.Always)]
private string ClassIDText {
set {
@@ -474,12 +491,16 @@ namespace ArchiSteamFarm.Json {
ClassID = classID;
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "marketable", Required = Required.Always)]
private byte MarketableNumber {
set => Marketable = value > 0;
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)]
private ImmutableHashSet<Tag> Tags {
set {
@@ -492,11 +513,14 @@ namespace ArchiSteamFarm.Json {
(Type, Rarity, RealAppID) = InterpretTags(value);
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "tradable", Required = Required.Always)]
private byte TradableNumber {
set => Tradable = value > 0;
}
#pragma warning restore IDE0051
[JsonConstructor]
private Description() { }
@@ -703,6 +727,7 @@ namespace ArchiSteamFarm.Json {
internal ulong TradeOfferID { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
private string TradeOfferIDText {
set {
@@ -721,6 +746,7 @@ namespace ArchiSteamFarm.Json {
TradeOfferID = tradeOfferID;
}
}
#pragma warning restore IDE0051
[JsonConstructor]
private TradeOfferSendResponse() { }

View File

@@ -728,5 +728,7 @@ StackTrace:
<value>Venter op til {0} for at sikre at vi er klar til at begynde at idle...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
</data>
<data name="UpdateCleanup" xml:space="preserve">
<value>Rydder op i de gamle filer efter opdateringen...</value>
</data>
</root>

View File

@@ -264,13 +264,16 @@ StackTrace:
<value>Εισάγετε το όνομα χρήστη Steam σας: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamParentalCode" xml:space="preserve">
<value>Εισάγετε το κωδικό γονικού ελέγχου του Steam σας: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputSteamPassword" xml:space="preserve">
<value>Εισάγετε τον κωδικό πρόσβασής σας στο Steam: </value>
<value>Εισάγετε τον κωδικό πρόσβαση του Steam: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="UserInputUnknown" xml:space="preserve">
<value>Εισάγετε τη μη τεκμηριωμένη τιμή {0}: </value>
<value>Εισάγετε τη μη τεκμηριωμένη τιμή του {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
@@ -280,7 +283,9 @@ StackTrace:
<data name="IPCReady" xml:space="preserve">
<value>Ο διακομιστής IPC είναι έτοιμος!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Έναρξη διακομιστή IPC...</value>
</data>
<data name="BotAlreadyStopped" xml:space="preserve">
<value>Το bot έχει ήδη σταματήσει!</value>
</data>
@@ -297,7 +302,7 @@ StackTrace:
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle, {3} will be replaced by total number of games to idle, {4} will be replaced by total number of cards to idle, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="BotStatusIdlingList" xml:space="preserve">
<value>Το bot συλλέγει κάρτες των παιχνιδιών: {0} από ένα σύνολο {1} παιχνιδιών ({2} κάρτες) που απομένουν (υπόλοιπο χρόνου ~{3}).</value>
<value>Το bot συλλέγει κάρτες των παιχνιδιών: {0} από ένα σύνολο {1} παιχνιδιών ({2} κάρτες) που απομένουν (υπόλοιπο χρόνου: ~{3}).</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to idle, {2} will be replaced by total number of cards to idle, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="CheckingFirstBadgePage" xml:space="preserve">
@@ -314,7 +319,7 @@ StackTrace:
<value>Ολοκληρώθηκε!</value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>Έχουμε συνολικά {0} παιχνίδια ({1} κάρτες) ακόμα για συλλογή καρτών (απομένουν ~{2})...</value>
<value>Έχουμε συνολικά {0} παιχνίδια ({1} κάρτες) ακόμα για συλλογή καρτών (υπόλοιπο χρόνου: ~{2})...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
@@ -335,7 +340,9 @@ StackTrace:
<data name="IdlingStopped" xml:space="preserve">
<value>Η συλλογή καρτών σταμάτησε!</value>
</data>
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
<value>Αγνοήθηκε το αίτημα, γιατί η μόνιμη παύση είναι ενεργοποιημένη!</value>
</data>
<data name="NothingToIdle" xml:space="preserve">
<value>Δεν έχουμε τίποτα να συλλέξουμε σε αυτόν τον λογαριασμό!</value>
</data>
@@ -440,7 +447,10 @@ StackTrace:
<value>Έγινε αποσύνδεση από το Steam: {0}</value>
<comment>{0} will be replaced by logging off reason (string)</comment>
</data>
<data name="BotLoggedOn" xml:space="preserve">
<value>Επιτυχής σύνδεση ως {0}.</value>
<comment>{0} will be replaced by steam ID (number)</comment>
</data>
<data name="BotLoggingIn" xml:space="preserve">
<value>Σύνδεση...</value>
</data>
@@ -473,7 +483,10 @@ StackTrace:
<value>Κατέχετε ήδη: {0} | {1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="BotRateLimitExceeded" xml:space="preserve">
<value>Ξεπεράστηκε το όριο προσπαθειών, θα ξαναγίνει προσπάθεια μετά από {0}...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
</data>
<data name="BotReconnecting" xml:space="preserve">
<value>Επανασύνδεση...</value>
</data>
@@ -577,7 +590,7 @@ StackTrace:
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
</data>
<data name="BotVersion" xml:space="preserve">
<value>{0} V{1}</value>
<value>{0} Εκδ.{1}</value>
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
</data>
<data name="BotAccountLocked" xml:space="preserve">
@@ -618,6 +631,10 @@ StackTrace:
<data name="BotRefreshingPackagesData" xml:space="preserve">
<value>Ανανέωση δεδομένων πακέτων...</value>
</data>
<data name="WarningDeprecated" xml:space="preserve">
<value>Η χρήση του {0} είναι υπό απόσυρση και θα αφαιρεθεί σε μελλοντικές εκδόσεις του προγράμματος. Παρακαλώ χρησιμοποιήστε το {1}.</value>
<comment>{0} will be replaced by the name of deprecated property (such as argument, config property or likewise), {1} will be replaced by the name of valid replacement (such as another argument or config property)</comment>
</data>
@@ -626,23 +643,45 @@ StackTrace:
<data name="ErrorAborted" xml:space="preserve">
<value>Ακυρώθηκε!</value>
</data>
<data name="PluginLoading" xml:space="preserve">
<value>Φόρτωση του {0} Εκδ.{1}...</value>
<comment>{0} will be replaced by the name of the custom ASF plugin, {1} will be replaced by its version</comment>
</data>
<data name="NothingFound" xml:space="preserve">
<value>Δεν βρέθηκε τίποτα!</value>
</data>
<data name="PleaseWait" xml:space="preserve">
<value>Παρακαλώ περιμένετε...</value>
</data>
<data name="EnterCommand" xml:space="preserve">
<value>Εισαγωγή εντολής: </value>
</data>
<data name="Executing" xml:space="preserve">
<value>Εκτέλεση...</value>
</data>
<data name="InteractiveConsoleEnabled" xml:space="preserve">
<value>Η διαδραστική κονσόλα είναι ενεργή, πατήστε 'c' για να εισάγετε εντολή.</value>
</data>
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
<value>Η διαδραστική κονσόλα δεν είναι διαθέσιμη διότι λείπει η ιδιότητα {0} από το αρχείο διαμόρφωσης.</value>
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
</data>
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
<value>Το bot έχει {0} παιχνίδια που απομένουν στην ουρά.</value>
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
</data>
<data name="UpdateCleanup" xml:space="preserve">
<value>Εκκαθάριση παλιών αρχείων μετά την ενημέρωση...</value>
</data>
</root>

View File

@@ -692,15 +692,42 @@ Trazo de pila:
<data name="PluginsWarning" xml:space="preserve">
<value>Has cargado uno o más plugins personalizados en el ASF. Ya que no podemos ofrecer soporte a configuraciones alteradas, por favor, consulte a los desarrolladores apropiados de los plugins que has decidido usar en caso de que tengas problemas.</value>
</data>
<data name="PleaseWait" xml:space="preserve">
<value>Por favor espera...</value>
</data>
<data name="EnterCommand" xml:space="preserve">
<value>Ingrese el comando: </value>
</data>
<data name="Executing" xml:space="preserve">
<value>Ejecutando...</value>
</data>
<data name="InteractiveConsoleEnabled" xml:space="preserve">
<value>La consola interactiva está activa, escriba 'c' para ingresar al modo de comando.</value>
</data>
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
<value>La consola interactiva no está disponible debido a que faltan {0} propiedades de configuración.</value>
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
</data>
<data name="Response" xml:space="preserve">
<value>Respuesta: {0}</value>
<comment>{0} will be replaced by the generated response (string)</comment>
</data>
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
<value>El bot tiene {0} juegos restantes en su cola de fondo.</value>
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
</data>
<data name="ErrorSingleInstanceRequired" xml:space="preserve">
<value>El proceso ASF ya está ejecutándose para este directorio de trabajo, ¡abortando!</value>
</data>
<data name="BotHandledConfirmations" xml:space="preserve">
<value>¡Se han manejado correctamente {0} confirmaciones!</value>
<comment>{0} will be replaced by number of confirmations</comment>
</data>
<data name="BotExtraIdlingCooldown" xml:space="preserve">
<value>Esperando hasta {0} para asegurar que estamos libres para empezar a recolectar...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
</data>
<data name="UpdateCleanup" xml:space="preserve">
<value>Limpiando archivos antiguos después de actualizar...</value>
</data>
</root>

View File

@@ -624,7 +624,7 @@ StackTrace :
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Fini de consulter la liste de découvertes #{0}.</value>
<value>Fin de lexploration de la liste de découvertes #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="BotOwnsOverviewPerGame" xml:space="preserve">
@@ -665,7 +665,7 @@ StackTrace :
<comment>{0} will be replaced by round number</comment>
</data>
<data name="DoneActivelyMatchingItems" xml:space="preserve">
<value>Fini de match des Items, round #{0}.</value>
<value>Fin de l'appariement des Items Steam, round #{0}.</value>
<comment>{0} will be replaced by round number</comment>
</data>
<data name="ErrorAborted" xml:space="preserve">
@@ -705,7 +705,10 @@ StackTrace :
<data name="InteractiveConsoleEnabled" xml:space="preserve">
<value>La console interactive est maintenant active, tapez 'c' pour entrer en mode commande.</value>
</data>
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
<value>La console interactive n'est pas disponible en raison de la propriété de configuration {0} manquante.</value>
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
</data>
<data name="Response" xml:space="preserve">
<value>Réponse : {0}</value>
<comment>{0} will be replaced by the generated response (string)</comment>
@@ -714,8 +717,18 @@ StackTrace :
<value>Le bot a {0} jeux restants dans sa file d'attente.</value>
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
</data>
<data name="ErrorSingleInstanceRequired" xml:space="preserve">
<value>Le processus ASF est déjà en cours d'exécution pour ce répertoire de travail, interruption !</value>
</data>
<data name="BotHandledConfirmations" xml:space="preserve">
<value>{0} confirmations gérées avec succès !</value>
<comment>{0} will be replaced by number of confirmations</comment>
</data>
<data name="BotExtraIdlingCooldown" xml:space="preserve">
<value>En attente de {0} pour s'assurer que nous pouvons commencer à récolter les cartes...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
</data>
<data name="UpdateCleanup" xml:space="preserve">
<value>Nettoyage des anciens fichiers après mise à jour...</value>
</data>
</root>

View File

@@ -697,7 +697,9 @@
</data>
<data name="InteractiveConsoleEnabled" xml:space="preserve">
<value>已開啟互動式主控台,按'C'回到指令模式</value>
</data>

View File

@@ -38,7 +38,13 @@ namespace ArchiSteamFarm {
private static Mutex SingleInstance;
internal static void CoreInit() {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !Console.IsOutputRedirected) {
// Normally we should use UTF-8 encoding as it's the most correct one for our case, and we already use it on other OSes such as Linux
// However, older Windows versions, mainly 7/8.1 can't into UTF-8 without appropriate console font, and expecting from users to change it manually is unwanted
// As irrational as it can sound, those versions actually can work with unicode encoding instead, as they magically map it into proper chars despite of incorrect font
// We could in theory conditionally use UTF-8 for Windows 10+ and unicode otherwise, but Windows version detection is simply not worth the hassle in this case
// Therefore, until we can drop support for Windows < 10, we'll stick with Unicode for all Windows boxes, unless there will be valid reasoning for conditional switch
// See https://github.com/JustArchiNET/ArchiSteamFarm/issues/1289 for more details
Console.OutputEncoding = Encoding.Unicode;
DisableQuickEditMode();
@@ -46,6 +52,12 @@ namespace ArchiSteamFarm {
}
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
ASF.ArchiLogger.LogNullError(nameof(optimizationMode));
return;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (systemRequired) {
KeepWindowsSystemActive();
@@ -78,6 +90,8 @@ namespace ArchiSteamFarm {
Mutex singleInstance = new Mutex(true, uniqueName, out bool result);
if (!result) {
singleInstance.Dispose();
return false;
}
@@ -111,10 +125,6 @@ namespace ArchiSteamFarm {
}
private static void DisableQuickEditMode() {
if (Console.IsOutputRedirected) {
return;
}
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);
@@ -137,8 +147,8 @@ namespace ArchiSteamFarm {
// More info: https://msdn.microsoft.com/library/windows/desktop/aa373208(v=vs.85).aspx
NativeMethods.EExecutionState result = NativeMethods.SetThreadExecutionState(NativeMethods.AwakeExecutionState);
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.Error) in our case
if (result == NativeMethods.EExecutionState.Error) {
// SetThreadExecutionState() returns NULL on failure, which is mapped to 0 (EExecutionState.None) in our case
if (result == NativeMethods.EExecutionState.None) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, result));
}
}
@@ -166,7 +176,7 @@ namespace ArchiSteamFarm {
[Flags]
internal enum EExecutionState : uint {
Error = 0,
None = 0,
SystemRequired = 0x00000001,
AwayModeRequired = 0x00000040,
Continuous = 0x80000000

View File

@@ -37,6 +37,8 @@ using SteamKit2;
namespace ArchiSteamFarm.Plugins {
internal static class PluginsCore {
internal static bool HasActivePluginsLoaded => ActivePlugins?.Count > 0;
[ImportMany]
private static ImmutableHashSet<IPlugin> ActivePlugins { get; set; }
@@ -47,7 +49,7 @@ namespace ArchiSteamFarm.Plugins {
return false;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return true;
}
@@ -66,7 +68,7 @@ namespace ArchiSteamFarm.Plugins {
[ItemNotNull]
internal static async Task<StringComparer> GetBotsComparer() {
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return StringComparer.Ordinal;
}
@@ -86,7 +88,7 @@ namespace ArchiSteamFarm.Plugins {
}
internal static bool InitPlugins() {
if ((ActivePlugins != null) && (ActivePlugins.Count > 0)) {
if (HasActivePluginsLoaded) {
return false;
}
@@ -147,6 +149,8 @@ namespace ArchiSteamFarm.Plugins {
ActivePlugins = activePlugins.ToImmutableHashSet();
ASF.ArchiLogger.LogGenericInfo(Strings.PluginsWarning);
Console.Title = SharedInfo.ProgramIdentifier;
return invalidPlugins.Count == 0;
}
@@ -181,7 +185,7 @@ namespace ArchiSteamFarm.Plugins {
}
internal static async Task OnASFInitModules(IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -200,7 +204,7 @@ namespace ArchiSteamFarm.Plugins {
return null;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return null;
}
@@ -224,7 +228,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -242,7 +246,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -260,7 +264,7 @@ namespace ArchiSteamFarm.Plugins {
return false;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return false;
}
@@ -284,7 +288,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -302,7 +306,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -320,7 +324,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -339,7 +343,7 @@ namespace ArchiSteamFarm.Plugins {
return null;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return null;
}
@@ -363,7 +367,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}
@@ -381,7 +385,7 @@ namespace ArchiSteamFarm.Plugins {
return null;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return null;
}
@@ -405,7 +409,7 @@ namespace ArchiSteamFarm.Plugins {
return false;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return false;
}
@@ -429,7 +433,7 @@ namespace ArchiSteamFarm.Plugins {
return;
}
if ((ActivePlugins == null) || (ActivePlugins.Count == 0)) {
if (!HasActivePluginsLoaded) {
return;
}

View File

@@ -27,7 +27,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC;
using ArchiSteamFarm.Localization;
@@ -131,10 +130,8 @@ namespace ArchiSteamFarm {
private static async Task InitASF(IReadOnlyCollection<string> args) {
OS.CoreInit();
string programIdentifier = SharedInfo.PublicIdentifier + " V" + SharedInfo.Version + " (" + SharedInfo.BuildInfo.Variant + "/" + SharedInfo.ModuleVersion + " | " + OS.Variant + ")";
Console.Title = programIdentifier;
ASF.ArchiLogger.LogGenericInfo(programIdentifier);
Console.Title = SharedInfo.ProgramIdentifier;
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
@@ -452,6 +449,16 @@ namespace ArchiSteamFarm {
return;
}
try {
string envPath = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariablePath);
if (!string.IsNullOrEmpty(envPath)) {
HandlePathArgument(envPath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
bool pathNext = false;
foreach (string arg in args) {

View File

@@ -23,6 +23,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using ArchiSteamFarm.Plugins;
using JetBrains.Annotations;
namespace ArchiSteamFarm {
@@ -37,6 +38,7 @@ namespace ArchiSteamFarm {
internal const string DatabaseExtension = ".db";
internal const string DebugDirectory = "debug";
internal const string EnvironmentVariableCryptKey = ASF + "_CRYPTKEY";
internal const string EnvironmentVariablePath = ASF + "_PATH";
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
internal const string GithubRepo = "JustArchiNET/" + AssemblyName;
internal const string GlobalConfigFileName = ASF + ConfigExtension;
@@ -57,14 +59,18 @@ namespace ArchiSteamFarm {
internal const string WebsiteDirectory = "www";
internal static string HomeDirectory => Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? throw new ArgumentNullException(nameof(HomeDirectory)));
internal static Guid ModuleVersion => Assembly.GetEntryAssembly()?.ManifestModule.ModuleVersionId ?? throw new ArgumentNullException(nameof(ModuleVersion));
[NotNull]
internal static string PublicIdentifier => AssemblyName + (BuildInfo.IsCustomBuild ? "-custom" : "");
internal static string ProgramIdentifier => PublicIdentifier + " V" + Version + " (" + BuildInfo.Variant + "/" + ModuleVersion + " | " + OS.Variant + ")";
[NotNull]
internal static string PublicIdentifier => AssemblyName + (BuildInfo.IsCustomBuild ? "-custom" : PluginsCore.HasActivePluginsLoaded ? "-modded" : "");
[NotNull]
internal static Version Version => Assembly.GetEntryAssembly()?.GetName().Version ?? throw new ArgumentNullException(nameof(Version));
private static Guid ModuleVersion => Assembly.GetEntryAssembly()?.ManifestModule.ModuleVersionId ?? throw new ArgumentNullException(nameof(ModuleVersion));
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal static class BuildInfo {
#if ASF_VARIANT_DOCKER

View File

@@ -139,7 +139,7 @@ namespace ArchiSteamFarm {
}
// Don't announce if we don't meet conditions
bool? eligible = await IsEligibleForMatching().ConfigureAwait(false);
bool? eligible = await IsEligibleForListing().ConfigureAwait(false);
if (!eligible.HasValue) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
@@ -235,6 +235,25 @@ namespace ArchiSteamFarm {
return objectResponse?.Content;
}
private async Task<bool?> IsEligibleForListing() {
bool? isEligibleForMatching = await IsEligibleForMatching().ConfigureAwait(false);
if (isEligibleForMatching != true) {
return isEligibleForMatching;
}
// Bot must have public inventory
bool? hasPublicInventory = await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false);
if (hasPublicInventory != true) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasPublicInventory) + ": " + (hasPublicInventory?.ToString() ?? "null")));
return hasPublicInventory;
}
return true;
}
private async Task<bool?> IsEligibleForMatching() {
// Bot must have ASF 2FA
if (!Bot.HasMobileAuthenticator) {
@@ -257,19 +276,10 @@ namespace ArchiSteamFarm {
return false;
}
// Bot must have public inventory
bool? hasPublicInventory = await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false);
if (!hasPublicInventory.GetValueOrDefault()) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasPublicInventory) + ": " + (hasPublicInventory?.ToString() ?? "null")));
return hasPublicInventory;
}
// Bot must have valid API key (e.g. not being restricted account)
bool? hasValidApiKey = await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false);
if (!hasValidApiKey.GetValueOrDefault()) {
if (hasValidApiKey != true) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.WarningFailedWithError, nameof(Bot.ArchiWebHandler.HasValidApiKey) + ": " + (hasValidApiKey?.ToString() ?? "null")));
return hasValidApiKey;
@@ -285,7 +295,7 @@ namespace ArchiSteamFarm {
return;
}
HashSet<Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(type => AcceptedMatchableTypes.Contains(type)).ToHashSet();
HashSet<Steam.Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
if (acceptedMatchableTypes.Count == 0) {
Bot.ArchiLogger.LogGenericTrace(Strings.ErrorAborted);
@@ -634,6 +644,7 @@ namespace ArchiSteamFarm {
internal bool MatchEverything { get; private set; }
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "matchable_backgrounds", Required = Required.Always)]
private byte MatchableBackgroundsNumber {
set {
@@ -653,7 +664,9 @@ namespace ArchiSteamFarm {
}
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "matchable_cards", Required = Required.Always)]
private byte MatchableCardsNumber {
set {
@@ -673,7 +686,9 @@ namespace ArchiSteamFarm {
}
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "matchable_emoticons", Required = Required.Always)]
private byte MatchableEmoticonsNumber {
set {
@@ -693,7 +708,9 @@ namespace ArchiSteamFarm {
}
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "matchable_foil_cards", Required = Required.Always)]
private byte MatchableFoilCardsNumber {
set {
@@ -713,7 +730,9 @@ namespace ArchiSteamFarm {
}
}
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "match_everything", Required = Required.Always)]
private byte MatchEverythingNumber {
set {
@@ -733,6 +752,7 @@ namespace ArchiSteamFarm {
}
}
}
#pragma warning restore IDE0051
[JsonConstructor]
private ListedUser() { }

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>ASF UI</title>
<meta http-equiv="refresh" content="0; url=http://127.0.0.1:1242">
<meta http-equiv="refresh" content="0; url=http://localhost:1242">
</head>
<body>
</body>

View File

@@ -21,11 +21,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

View File

@@ -34,15 +34,18 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
public sealed class WebBrowser : IDisposable {
[PublicAPI]
public const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
internal const byte MaxConnections = 5; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
private const byte MaxIdleTime = 15; // Defines in seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
internal readonly CookieContainer CookieContainer = new CookieContainer();
[PublicAPI]
public TimeSpan Timeout => HttpClient.Timeout;
internal TimeSpan Timeout => HttpClient.Timeout;
internal readonly CookieContainer CookieContainer = new CookieContainer();
private readonly ArchiLogger ArchiLogger;
private readonly HttpClient HttpClient;
@@ -605,31 +608,21 @@ namespace ArchiSteamFarm {
redirectUri = new Uri(requestUri, redirectUri);
}
response.Dispose();
// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a fragment should inherit the fragment from the original URI
if (!string.IsNullOrEmpty(requestUri.Fragment) && string.IsNullOrEmpty(redirectUri.Fragment)) {
redirectUri = new UriBuilder(redirectUri) { Fragment = requestUri.Fragment }.Uri;
}
// According to the RFC, POST requests in certain types of redirection must be converted into GET
if (httpMethod == HttpMethod.Post) {
switch (response.StatusCode) {
case HttpStatusCode.Found:
case HttpStatusCode.Moved:
case HttpStatusCode.MultipleChoices:
case HttpStatusCode.SeeOther:
httpMethod = HttpMethod.Get;
data = null;
break;
}
}
response.Dispose();
return await InternalRequest(redirectUri, httpMethod, data, referer, httpCompletionOption, --maxRedirections).ConfigureAwait(false);
}
if (response.StatusCode.IsClientErrorCode()) {
if (Debugging.IsUserDebugging) {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, await response.Content.ReadAsStringAsync().ConfigureAwait(false)));
}
// Do not retry on client errors
return response;
}

View File

@@ -24,7 +24,7 @@
[![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi)
[![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/5eur)
[![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
[![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://www.blockchain.com/btc/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT)
[![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockstream.info/address/bc1q8archy9jneaqw6s3cs44azt6duyqdt8c6quml0)
[![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
---

17
SECURITY.md Normal file
View File

@@ -0,0 +1,17 @@
# Security policy
---
## Supported versions
We support **[the latest stable](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** release only. In addition to that, limited support applies to **[the latest pre-release](https://github.com/JustArchiNET/ArchiSteamFarm/releases)** version (if available). Check out our **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** for more info.
---
## Reporting a vulnerability
We're doing our best to protect our community from all harm, therefore we take security vulnerabilities very seriously.
If you believe that you've found one, we'd appreciate if you let us know about it. You can do so by contacting us privately at ASF@JustArchi.net e-mail, where we'll do our best to evaluate your issue ASAP and keep you updated with the development status. If your vulnerability isn't crucial and doesn't result in a direct escalation, therefore can be known publicly while the appropriate fix is being implemented, you can also open a standard **[issue](https://github.com/JustArchiNET/ArchiSteamFarm/issues/new/choose)** instead.
Depending on the severity of the issue, we might take further actions in order to limit potential damage, for example by speeding up the release of the next stable ASF version. This is evaluated on a case-by-case basis.

7
SUPPORT.md Normal file
View File

@@ -0,0 +1,7 @@
# Support
Our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** is the official online documentation which covers at least a significant majority (if not all) of ASF subjects you could be interested in. We recommend to start with **[setting up](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Setting-up)**, **[configuration](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration)** and our **[FAQ](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/FAQ)** which should help you with setting up ASF, configuring it, as well as answering the most common questions that you might have. For more advanced matters, as well as further elaboration, we have other pages available on our **[wiki](https://github.com/JustArchiNET/ArchiSteamFarm/wiki)** that you can visit.
We also have two 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 on our **[Steam group](https://steamcommunity.com/groups/archiasf/discussions/1)**, which should be used for majority of the issues and questions, especially advanced ones. In addition to that, we have a **[Discord server](https://discord.gg/hSQgt8j)**, also known as "ASF chat", which is a good choice for more casual, simple questions. 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 Steam group, where we're doing our best to answer all questions that couldn't be answered by our community itself (as opposed to ASF chat where we're not active 24/7 and therefore we're not able to help everybody).
GitHub **[issues](https://github.com/JustArchiNET/ArchiSteamFarm/issues)** page is being used for ASF development, especially in regards to bugs and enhancements. We have a very strict policy regarding that, as GitHub 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. If you're not sure whether your matter relates to ASF development or not, we recommend to use a support channel instead. Invalid GitHub issues will be closed immediately and won't be answered.

View File

@@ -257,7 +257,7 @@ deploy:
- provider: GitHub
tag: $(appveyor_repo_tag_name)
release: ArchiSteamFarm V$(appveyor_repo_tag_name)
description: '### Notice\n\n**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.**\n\n---\n\n### Changelog\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchiNET/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\n### Support\n\nASF 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!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/5eur) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://www.blockchain.com/btc/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
description: '### Notice\n\n**Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchiNET/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.**\n\n---\n\n### Changelog\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchiNET/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\n### Support\n\nASF 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!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/5eur) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockstream.info/address/bc1q8archy9jneaqw6s3cs44azt6duyqdt8c6quml0) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
auth_token:
secure: uYK1wf3znNdkuQqImXR6rZ94ESgzF1vJHCAcJ75Y+m+/pc/Ro6cikzy6O7DVZ39T
artifact: /.*/

2
wiki

Submodule wiki updated: b65ec70ac9...8adf2672bf