├── .gitattributes ├── .gitignore ├── .nuget ├── Microsoft.Build.dll ├── NuGet.Config ├── NuGet.targets └── packages.config ├── Bin ├── Debug │ └── settings-template.json └── Release │ └── settings-template.json ├── CONTRIBUTING.md ├── Docs ├── Json │ └── 753_6.txt └── inventories.htm ├── LICENSE ├── README.md ├── SteamBot.sln ├── SteamBot ├── 3rdparty │ └── Options.cs ├── AdminUserHandler.cs ├── AssemblyInfo.cs ├── Bot.cs ├── BotInfo.cs ├── BotManager.cs ├── BotManagerInterpreter.cs ├── Configuration.cs ├── ExampleBot.csproj ├── ExampleBot.csproj.DotSettings ├── Log.cs ├── Notifications.cs ├── Program.cs ├── SimpleUserHandler.cs ├── SteamGroups │ ├── CMsgGroupInviteAction.cs │ └── CMsgInviteUserToGroup.cs ├── SteamGuardRequiredEventArgs.cs ├── SteamTradeDemoHandler.cs ├── TF2GC │ ├── Crafting.cs │ ├── Items.cs │ ├── MsgCraft.cs │ └── MsgDelete.cs ├── TradeOfferUserHandler.cs ├── UserHandler.cs ├── app.config └── packages.config ├── SteamBotUnitTest.sln ├── SteamBotUnitTest ├── Properties │ └── AssemblyInfo.cs ├── SteamTrade │ └── Tf2ValueTests.cs ├── SteamTradeUnitTest.csproj └── packages.config ├── SteamTrade ├── AssemblyInfo.cs ├── Exceptions │ ├── InventoryFetchException.cs │ └── TradeException.cs ├── ForeignInventory.cs ├── GenericInventory.cs ├── Inventory.cs ├── Schema.cs ├── SteamTrade.csproj ├── SteamWeb.cs ├── Tf2Value.cs ├── Trade.cs ├── TradeManager.cs ├── TradeOffer │ ├── OfferSession.cs │ ├── TradeOffer.cs │ ├── TradeOfferManager.cs │ └── TradeOfferWebAPI.cs ├── TradeWebAPI │ ├── TradeSession.cs │ └── TradeStatus.cs ├── app.config └── packages.config └── triggerappveyor.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj 2 | [Rr]elease 3 | [Dd]ebug 4 | packages 5 | *.exe 6 | *.dll 7 | *.user 8 | *.suo 9 | *.[Cc]ache 10 | *.bak 11 | *.ncb 12 | *.log 13 | *.DS_Store 14 | [Tt]humbs.db 15 | _ReSharper.* 16 | *.resharper 17 | Ankh.NoLoad 18 | 19 | #OS junk files 20 | [Tt]humbs.db 21 | *.DS_Store 22 | 23 | *.userprefs 24 | *.pidb 25 | 26 | # NuGet Packages Directory 27 | packages 28 | *.nupkg 29 | 30 | # Allow NuGet specific exes and dlls 31 | !.nuget/Microsoft.Build.dll 32 | -------------------------------------------------------------------------------- /.nuget/Microsoft.Build.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jessecar96/SteamBot/e8e9e5fcd64ae35b201e2597068849c10a667b60/.nuget/Microsoft.Build.dll -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 32 | 33 | 34 | 35 | 36 | $(SolutionDir).nuget 37 | packages.config 38 | 39 | 40 | 41 | 42 | $(NuGetToolsPath)\NuGet.exe 43 | @(PackageSource) 44 | 45 | "$(NuGetExePath)" 46 | mono --runtime=v4.0.30319 $(NuGetExePath) 47 | 48 | $(TargetDir.Trim('\\')) 49 | 50 | -RequireConsent 51 | -NonInteractive 52 | 53 | 54 | $(SolutionDir) 55 | $(SolutionDir) 56 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDirParsed)" 57 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDirParsed)" 58 | 59 | $(NuGetCommand) pack "$(ProjectPath)" -Properties Configuration=$(Configuration) $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 60 | 61 | 62 | 63 | RestorePackages; 64 | $(BuildDependsOn); 65 | 66 | 67 | 68 | 69 | $(BuildDependsOn); 70 | BuildPackage; 71 | 72 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Bin/Debug/settings-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Admins":["234567"], 3 | "ApiKey":"Steam API Key goes here!", 4 | "mainLog": "syslog.log", 5 | "UseSeparateProcesses": "false", 6 | "AutoStartAllBots": "true", 7 | "Bots": [ 8 | { 9 | "Username":"BOT USERNAME", 10 | "Password":"BOT PASSWORD", 11 | "ApiKey":"Bot specific apiKey", 12 | "DisplayName":"TestBot", 13 | "ChatResponse":"Hi there bro", 14 | "logFile": "TestBot.log", 15 | "BotControlClass": "SteamBot.SimpleUserHandler", 16 | "MaximumTradeTime":180, 17 | "MaximumActionGap":30, 18 | "DisplayNamePrefix":"[AutomatedBot] ", 19 | "TradePollingInterval":800, 20 | "TradeOfferPollingIntervalSecs":30, 21 | "ConsoleLogLevel":"Success", 22 | "FileLogLevel":"Info", 23 | "AutoStart": "true", 24 | "SchemaLang":"en" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /Bin/Release/settings-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Admins":["234567"], 3 | "ApiKey":"Steam API Key goes here!", 4 | "mainLog": "syslog.log", 5 | "UseSeparateProcesses": "false", 6 | "AutoStartAllBots": "true", 7 | "Bots": [ 8 | { 9 | "Username":"BOT USERNAME", 10 | "Password":"BOT PASSWORD", 11 | "ApiKey":"Bot specific apiKey", 12 | "DisplayName":"TestBot", 13 | "ChatResponse":"Hi there bro", 14 | "logFile": "TestBot.log", 15 | "BotControlClass": "SteamBot.SimpleUserHandler", 16 | "MaximumTradeTime":180, 17 | "MaximumActionGap":30, 18 | "DisplayNamePrefix":"[AutomatedBot] ", 19 | "TradePollingInterval":800, 20 | "TradeOfferPollingIntervalSecs":30, 21 | "ConsoleLogLevel":"Success", 22 | "FileLogLevel":"Info", 23 | "AutoStart": "true", 24 | "SchemaLang":"en" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SteamBot # 2 | When you're contributing to SteamBot, there are a few rules you should follow. First and foremost, SteamBot should be able to compile and run on Linux. SteamBot development works in both Visual Studio and MonoDevelop, but _please_ keep your temporary files (such as `.pidb`, `.*~`, or even `.tmp`) out of the project. 3 | 4 | ## How To Contribute ## 5 | 1. Fork The Repository ([Jessecar96/SteamBot](https://github.com/Jessecar96/SteamBot)) 6 | 2. Branch It 7 | - this is because when you do the pull request for it, it includes commits you make after you make the pull request and before the pull request is accepted 8 | 3. Make Your Changes 9 | 4. Commit Your Changes 10 | 5. Do 3 and 4 as Needed 11 | 6. Push Your Changes Back to GitHub 12 | 7. Start a Pull Request on the Repository 13 | - make sure you explain what the pull request does 14 | 15 | ## Indentation ## 16 | With SteamBot, you should use four (4) spaces as an indent; tabs should not be used as indentation ever. This comes from 17 | Microsoft's [C# Coding Conventions](http://msdn.microsoft.com/en-us/library/vstudio/ff926074.aspx) (thank you, Philipp). It gets annoying when you have both in there, and it clogs up commit logs trying to fix it. 18 | 19 | ## Brackets ## 20 | Brackets should be on the next line of a function definition or an if directive. Brackets should always be on their own line. 21 | 22 | ## Issues ## 23 | Make sure you 24 | - Describe the problem 25 | - Show how to reproduce it, if applicable 26 | - Explain what you think is causing it, if applicable 27 | - Give a plausible solution 28 | 29 | ## Commits ## 30 | Commits should be in the present tense, and with Title Capitalization. If needed, a body should be on the next line in normal capitalization. 31 | 32 | ## C# 6 ## 33 | It would be better to stick to C# 5 features to be compatible with #1002. 34 | -------------------------------------------------------------------------------- /Docs/Json/753_6.txt: -------------------------------------------------------------------------------- 1 | { 2 | "success":true, 3 | "rgInventory": 4 | { 5 | "70968777": 6 | { 7 | "id":"70968777", 8 | "classid":"172884566", 9 | "instanceid":"0", 10 | "amount":"1", 11 | "pos":1 12 | } 13 | }, 14 | 15 | "rgCurrency":[], 16 | 17 | "rgDescriptions": 18 | { 19 | "172884566_0": 20 | { 21 | "appid":"753", 22 | "classid":"172884566", 23 | "instanceid":"0", 24 | "icon_url":"LM8bG-H8rsNnAQorXQCek5SD0aqljKCDs8_ZMbnm1jTlxCzguODo0cfYFtV7YNAZmZbIufDO4pu4wsQpuOXXM-jAP6Cm6PvTxoVa02Iqzh7Tw5Xqr4y427WZ1WPxrZMwvZ0_t6vkr9KRlEHfdy2XWMrFlL_7muvX4p-HaafwjDv0zg==","icon_url_large":"LM8bG-H8rsNnAQorXQCek5SD0aqljKCDs8_ZMbnm1jTlxCzguODo0cfYFtV7YNAZmZbIufDO4pu4wsQpuOXXM-jAP6Cm6PvTxoVa02Iqzh7Tw5Xqr4y427WZ1WPxrZMwvZ0_t6vkr9KRlEHfdy2XWMrFlL_7muvX4p-HaafwjDv0zg==", 25 | "icon_drag_url":"", 26 | "name":"GM_Construct", 27 | "market_hash_name":"4000-GM_Construct", 28 | "market_name":"GM_Construct", 29 | "name_color":"", 30 | "background_color":"", 31 | "type":"Garry's Mod Profile Background", 32 | "tradable":1, 33 | "marketable":1, 34 | "market_fee_app":"4000", 35 | 36 | "descriptions":[{"value":"The map that spawned a million melons"}], 37 | 38 | "tags":[{"internal_name":"app_4000","name":"Garry's Mod","category":"Game","category_name":"Game"}, 39 | {"internal_name":"droprate_0","name":"Common","category":"droprate","category_name":"Rarity"}, 40 | {"internal_name":"item_class_3","name":"Profile Background","category":"item_class","category_name":"Item Type"}] 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Docs/inventories.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Non TF2 Inventories 6 | 16 | 17 | 18 |

Version 1.1 (2013/08/18)

19 |

Known Inventories

20 |

21 | Base URL: http://steamcommunity.com/id/userName/inventory/json/AppId/ContextId
22 | Game Appids List: http://api.steampowered.com/ISteamApps/GetAppList/v2
23 | NOTE: Inventory Context Id (type) changes according to the AppId item schema, so it's up to you find it out wich ones work :/ 24 |

25 | 26 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
Steam Inventory (AppId 753)
Context IdDescriptionNotes
1Gifts (Games)WARNING: Automatization (buy & sell) may be against Steam TOS
3Coupons
6Steam Trading CardsGame Cards, Profile Backgrounds & Emoticons
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
Counter-Strike: Global Offensive (AppId 730)
Context IdDescriptionNotes
2InventoryWeapons
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Dota 2 (AppId 570)
Context IdDescriptionNotes
2Main InventoryNot sure
81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) SteamBot Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/r2ml39xoa5svu61y/branch/master?svg=true)](https://ci.appveyor.com/project/Jessecar96/steambot/branch/master) 2 | 3 | **SteamBot** is a bot written in C# for the purpose of interacting with Steam Chat and Steam Trade. As of right now, about 8 contributors have all added to the bot. The bot is publicly available under the MIT License. Check out [LICENSE] for more details. 4 | 5 | There are several things you must do in order to get SteamBot working: 6 | 7 | 1. Download the source. 8 | 2. Compile the source code. 9 | 3. Configure the bot (username, password, etc.). 10 | 4. *Optionally*, customize the bot by changing the source code. 11 | 12 | ## Getting the Source 13 | 14 | Retrieving the source code should be done by following the [installation guide] on the wiki. The install guide covers the instructions needed to obtain the source code as well as the instructions for compiling the code. 15 | 16 | ## Configuring the Bot 17 | 18 | See the [configuration guide] on the wiki. This guide covers configuring a basic bot as well as creating a custom user handler. 19 | 20 | ## Bot Administration 21 | 22 | While running the bots you may find it necessary to do some basic operations like shutting down and restarting a bot. The console will take some commands to allow you to do some this. See the [usage guide] for more information. 23 | 24 | ## More help? 25 | If it's a bug, open an Issue; if you have a fix, read [CONTRIBUTING.md] and open a Pull Request. If it is a question about how to use SteamBot with your own bots, visit our subreddit at [/r/SteamBot](http://www.reddit.com/r/SteamBot). Please use the issue tracker only for bugs reports and pull requests. The subreddit should be used for all other discussions. 26 | 27 | 28 | A list of contributors (add yourself if you want to): 29 | 30 | - [Jessecar96](http://steamcommunity.com/id/jessecar) (project lead) 31 | - [geel9](http://steamcommunity.com/id/geel9) 32 | - [cwhelchel](http://steamcommunity.com/id/cmw69krinkle) 33 | - [Lagg](http://lagg.me) 34 | - [BlueRaja](http://steamcommunity.com/id/BlueRaja/) 35 | 36 | ## Wanna Contribute? 37 | Please read [CONTRIBUTING.md]. 38 | 39 | 40 | [installation guide]: https://github.com/Jessecar96/SteamBot/wiki/Installation-Guide 41 | [CONTRIBUTING.md]: https://github.com/Jessecar96/SteamBot/blob/master/CONTRIBUTING.md 42 | [LICENSE]: https://github.com/Jessecar96/SteamBot/blob/master/LICENSE 43 | [configuration guide]: https://github.com/Jessecar96/SteamBot/wiki/Configuration-Guide 44 | [usage guide]: https://github.com/Jessecar96/SteamBot/wiki/Usage-Guide 45 | -------------------------------------------------------------------------------- /SteamBot.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2013 for Windows Desktop 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleBot", "SteamBot\ExampleBot.csproj", "{E81DED36-EDF5-41A5-8666-A3A0C581762F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamTrade", "SteamTrade\SteamTrade.csproj", "{6CEC0333-81EB-40EE-85D1-941363626FC7}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C8129CC9-FBBA-4F80-831B-94BDE64D263C}" 11 | ProjectSection(SolutionItems) = preProject 12 | .nuget\Microsoft.Build.dll = .nuget\Microsoft.Build.dll 13 | .nuget\NuGet.Config = .nuget\NuGet.Config 14 | .nuget\NuGet.targets = .nuget\NuGet.targets 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {E81DED36-EDF5-41A5-8666-A3A0C581762F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {E81DED36-EDF5-41A5-8666-A3A0C581762F}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {E81DED36-EDF5-41A5-8666-A3A0C581762F}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {E81DED36-EDF5-41A5-8666-A3A0C581762F}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {6CEC0333-81EB-40EE-85D1-941363626FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {6CEC0333-81EB-40EE-85D1-941363626FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {6CEC0333-81EB-40EE-85D1-941363626FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {6CEC0333-81EB-40EE-85D1-941363626FC7}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(MonoDevelopProperties) = preSolution 36 | StartupItem = SteamBot\ExampleBot.csproj 37 | Policies = $0 38 | $0.DotNetNamingPolicy = $1 39 | $1.DirectoryNamespaceAssociation = None 40 | $1.ResourceNamePolicy = FileFormatDefault 41 | $0.TextStylePolicy = $5 42 | $2.inheritsSet = null 43 | $2.scope = text/x-csharp 44 | $0.CSharpFormattingPolicy = $3 45 | $3.AnonymousMethodBraceStyle = NextLine 46 | $3.PropertyBraceStyle = NextLine 47 | $3.PropertyGetBraceStyle = NextLine 48 | $3.PropertySetBraceStyle = NextLine 49 | $3.EventBraceStyle = NextLine 50 | $3.EventAddBraceStyle = NextLine 51 | $3.EventRemoveBraceStyle = NextLine 52 | $3.StatementBraceStyle = NextLine 53 | $3.ElseNewLinePlacement = NewLine 54 | $3.CatchNewLinePlacement = NewLine 55 | $3.FinallyNewLinePlacement = NewLine 56 | $3.inheritsSet = Mono 57 | $3.inheritsScope = text/x-csharp 58 | $3.scope = text/x-csharp 59 | $4.FileWidth = 120 60 | $4.inheritsSet = VisualStudio 61 | $4.inheritsScope = text/plain 62 | $4.scope = text/plain 63 | $5.inheritsSet = null 64 | $5.scope = application/octet-stream 65 | $0.StandardHeader = $6 66 | $6.Text = 67 | $6.IncludeInNewFiles = True 68 | $0.NameConventionPolicy = $7 69 | $7.Rules = $8 70 | $8.NamingRule = $33 71 | $9.Name = Namespaces 72 | $9.AffectedEntity = Namespace 73 | $9.VisibilityMask = VisibilityMask 74 | $9.NamingStyle = PascalCase 75 | $9.IncludeInstanceMembers = True 76 | $9.IncludeStaticEntities = True 77 | $10.Name = Types 78 | $10.AffectedEntity = Class, Struct, Enum, Delegate 79 | $10.VisibilityMask = VisibilityMask 80 | $10.NamingStyle = PascalCase 81 | $10.IncludeInstanceMembers = True 82 | $10.IncludeStaticEntities = True 83 | $11.Name = Interfaces 84 | $11.RequiredPrefixes = $12 85 | $12.String = I 86 | $11.AffectedEntity = Interface 87 | $11.VisibilityMask = VisibilityMask 88 | $11.NamingStyle = PascalCase 89 | $11.IncludeInstanceMembers = True 90 | $11.IncludeStaticEntities = True 91 | $13.Name = Attributes 92 | $13.RequiredSuffixes = $14 93 | $14.String = Attribute 94 | $13.AffectedEntity = CustomAttributes 95 | $13.VisibilityMask = VisibilityMask 96 | $13.NamingStyle = PascalCase 97 | $13.IncludeInstanceMembers = True 98 | $13.IncludeStaticEntities = True 99 | $15.Name = Event Arguments 100 | $15.RequiredSuffixes = $16 101 | $16.String = EventArgs 102 | $15.AffectedEntity = CustomEventArgs 103 | $15.VisibilityMask = VisibilityMask 104 | $15.NamingStyle = PascalCase 105 | $15.IncludeInstanceMembers = True 106 | $15.IncludeStaticEntities = True 107 | $17.Name = Exceptions 108 | $17.RequiredSuffixes = $18 109 | $18.String = Exception 110 | $17.AffectedEntity = CustomExceptions 111 | $17.VisibilityMask = VisibilityMask 112 | $17.NamingStyle = PascalCase 113 | $17.IncludeInstanceMembers = True 114 | $17.IncludeStaticEntities = True 115 | $19.Name = Methods 116 | $19.AffectedEntity = Methods 117 | $19.VisibilityMask = VisibilityMask 118 | $19.NamingStyle = PascalCase 119 | $19.IncludeInstanceMembers = True 120 | $19.IncludeStaticEntities = True 121 | $20.Name = Static Readonly Fields 122 | $20.AffectedEntity = ReadonlyField 123 | $20.VisibilityMask = Internal, Protected, Public 124 | $20.NamingStyle = PascalCase 125 | $20.IncludeInstanceMembers = False 126 | $20.IncludeStaticEntities = True 127 | $21.Name = Fields (Non Private) 128 | $21.AffectedEntity = Field 129 | $21.VisibilityMask = Internal, Protected, Public 130 | $21.NamingStyle = PascalCase 131 | $21.IncludeInstanceMembers = True 132 | $21.IncludeStaticEntities = True 133 | $22.Name = ReadOnly Fields (Non Private) 134 | $22.AffectedEntity = ReadonlyField 135 | $22.VisibilityMask = Internal, Protected, Public 136 | $22.NamingStyle = PascalCase 137 | $22.IncludeInstanceMembers = True 138 | $22.IncludeStaticEntities = False 139 | $23.Name = Fields (Private) 140 | $23.AllowedPrefixes = $24 141 | $24.String = m_ 142 | $23.AffectedEntity = Field, ReadonlyField 143 | $23.VisibilityMask = Private 144 | $23.NamingStyle = CamelCase 145 | $23.IncludeInstanceMembers = True 146 | $23.IncludeStaticEntities = False 147 | $25.Name = Static Fields (Private) 148 | $25.AffectedEntity = Field 149 | $25.VisibilityMask = Private 150 | $25.NamingStyle = CamelCase 151 | $25.IncludeInstanceMembers = False 152 | $25.IncludeStaticEntities = True 153 | $26.Name = ReadOnly Fields (Private) 154 | $26.AllowedPrefixes = $27 155 | $27.String = m_ 156 | $26.AffectedEntity = ReadonlyField 157 | $26.VisibilityMask = Private 158 | $26.NamingStyle = CamelCase 159 | $26.IncludeInstanceMembers = True 160 | $26.IncludeStaticEntities = False 161 | $28.Name = Constant Fields 162 | $28.AffectedEntity = ConstantField 163 | $28.VisibilityMask = VisibilityMask 164 | $28.NamingStyle = PascalCase 165 | $28.IncludeInstanceMembers = True 166 | $28.IncludeStaticEntities = True 167 | $29.Name = Properties 168 | $29.AffectedEntity = Property 169 | $29.VisibilityMask = VisibilityMask 170 | $29.NamingStyle = PascalCase 171 | $29.IncludeInstanceMembers = True 172 | $29.IncludeStaticEntities = True 173 | $30.Name = Events 174 | $30.AffectedEntity = Event 175 | $30.VisibilityMask = VisibilityMask 176 | $30.NamingStyle = PascalCase 177 | $30.IncludeInstanceMembers = True 178 | $30.IncludeStaticEntities = True 179 | $31.Name = Enum Members 180 | $31.AffectedEntity = EnumMember 181 | $31.VisibilityMask = VisibilityMask 182 | $31.NamingStyle = PascalCase 183 | $31.IncludeInstanceMembers = True 184 | $31.IncludeStaticEntities = True 185 | $32.Name = Parameters 186 | $32.AffectedEntity = Parameter 187 | $32.VisibilityMask = VisibilityMask 188 | $32.NamingStyle = CamelCase 189 | $32.IncludeInstanceMembers = True 190 | $32.IncludeStaticEntities = True 191 | $33.Name = Type Parameters 192 | $33.RequiredPrefixes = $34 193 | $34.String = T 194 | $33.AffectedEntity = TypeParameter 195 | $33.VisibilityMask = VisibilityMask 196 | $33.NamingStyle = PascalCase 197 | $33.IncludeInstanceMembers = True 198 | $33.IncludeStaticEntities = True 199 | outputpath = ..\Bin 200 | EndGlobalSection 201 | EndGlobal 202 | -------------------------------------------------------------------------------- /SteamBot/AdminUserHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using SteamKit2; 4 | using SteamTrade; 5 | using System.Collections.Generic; 6 | using SteamTrade.TradeOffer; 7 | 8 | namespace SteamBot 9 | { 10 | /// 11 | /// A user handler class that implements basic text-based commands entered in 12 | /// chat or trade chat. 13 | /// 14 | public class AdminUserHandler : UserHandler 15 | { 16 | private const string AddCmd = "add"; 17 | private const string RemoveCmd = "remove"; 18 | private const string AddCratesSubCmd = "crates"; 19 | private const string AddWepsSubCmd = "weapons"; 20 | private const string AddMetalSubCmd = "metal"; 21 | private const string AddAllSubCmd = "all"; 22 | private const string HelpCmd = "help"; 23 | 24 | public AdminUserHandler(Bot bot, SteamID sid) : base(bot, sid) {} 25 | 26 | #region Overrides of UserHandler 27 | 28 | /// 29 | /// Called when the bot is fully logged in. 30 | /// 31 | public override void OnLoginCompleted() 32 | { 33 | } 34 | 35 | /// 36 | /// Triggered when a clan invites the bot. 37 | /// 38 | /// 39 | /// Whether to accept. 40 | /// 41 | public override bool OnGroupAdd() 42 | { 43 | return false; 44 | } 45 | 46 | /// 47 | /// Called when a the user adds the bot as a friend. 48 | /// 49 | /// 50 | /// Whether to accept. 51 | /// 52 | public override bool OnFriendAdd() 53 | { 54 | // if the other is an admin then accept add 55 | if (IsAdmin) 56 | { 57 | return true; 58 | } 59 | 60 | Log.Warn("Random SteamID: " + OtherSID + " tried to add the bot as a friend"); 61 | return false; 62 | } 63 | 64 | public override void OnFriendRemove() 65 | { 66 | } 67 | 68 | /// 69 | /// Called whenever a message is sent to the bot. 70 | /// This is limited to regular and emote messages. 71 | /// 72 | public override void OnMessage(string message, EChatEntryType type) 73 | { 74 | // TODO: magic command system 75 | } 76 | 77 | /// 78 | /// Called whenever a user requests a trade. 79 | /// 80 | /// 81 | /// Whether to accept the request. 82 | /// 83 | public override bool OnTradeRequest() 84 | { 85 | if (IsAdmin) 86 | return true; 87 | 88 | return false; 89 | } 90 | 91 | public override void OnTradeError(string error) 92 | { 93 | Log.Error(error); 94 | } 95 | 96 | public override void OnTradeTimeout() 97 | { 98 | Log.Warn("Trade timed out."); 99 | } 100 | 101 | public override void OnTradeInit() 102 | { 103 | SendTradeMessage("Success. (Type {0} for commands)", HelpCmd); 104 | } 105 | 106 | public override void OnTradeAddItem(Schema.Item schemaItem, Inventory.Item inventoryItem) 107 | { 108 | // whatever. 109 | } 110 | 111 | public override void OnTradeRemoveItem(Schema.Item schemaItem, Inventory.Item inventoryItem) 112 | { 113 | // whatever. 114 | } 115 | 116 | public override void OnTradeMessage(string message) 117 | { 118 | ProcessTradeMessage(message); 119 | } 120 | 121 | public override void OnTradeReady(bool ready) 122 | { 123 | if (!IsAdmin) 124 | { 125 | SendTradeMessage("You are not my master."); 126 | Trade.SetReady(false); 127 | return; 128 | } 129 | 130 | Trade.SetReady(true); 131 | } 132 | 133 | public override void OnTradeOfferUpdated(TradeOffer offer) 134 | { 135 | if(offer.OfferState == TradeOfferState.TradeOfferStateAccepted) 136 | { 137 | Log.Success("Trade Complete."); 138 | } 139 | } 140 | 141 | public override void OnTradeAwaitingConfirmation(long tradeOfferID) 142 | { 143 | Log.Warn("Trade ended awaiting confirmation"); 144 | SendChatMessage("Please complete the confirmation to finish the trade"); 145 | } 146 | 147 | public override void OnTradeAccept() 148 | { 149 | if (IsAdmin) 150 | { 151 | //Even if it is successful, AcceptTrade can fail on 152 | //trades with a lot of items so we use a try-catch 153 | try 154 | { 155 | if (Trade.AcceptTrade()) 156 | Log.Success("Trade Accepted!"); 157 | } 158 | catch 159 | { 160 | Log.Warn("The trade might have failed, but we can't be sure."); 161 | } 162 | } 163 | } 164 | 165 | #endregion 166 | 167 | private void ProcessTradeMessage(string message) 168 | { 169 | if (message.Equals(HelpCmd)) 170 | { 171 | PrintHelpMessage(); 172 | return; 173 | } 174 | 175 | if (message.StartsWith(AddCmd)) 176 | { 177 | HandleAddCommand(message); 178 | SendTradeMessage("done adding."); 179 | } 180 | else if (message.StartsWith(RemoveCmd)) 181 | { 182 | HandleRemoveCommand(message); 183 | SendTradeMessage("done removing."); 184 | } 185 | } 186 | 187 | private void PrintHelpMessage() 188 | { 189 | SendTradeMessage("{0} {1} [amount] [series] - adds all crates (optionally by series number, use 0 for amount to add all)", AddCmd, AddCratesSubCmd); 190 | SendTradeMessage("{0} {1} [amount] - adds metal", AddCmd, AddMetalSubCmd); 191 | SendTradeMessage("{0} {1} [amount] - adds weapons", AddCmd, AddWepsSubCmd); 192 | SendTradeMessage("{0} {1} [amount] - adds items", AddCmd, AddAllSubCmd); 193 | SendTradeMessage(@"{0} [amount] - adds all or a given amount of items of a given crafting type.", AddCmd); 194 | SendTradeMessage(@"{0} [amount] - adds all or a given amount of items of a given defindex.", AddCmd); 195 | 196 | SendTradeMessage(@"See http://wiki.teamfortress.com/wiki/WebAPI/GetSchema for info about craft_material_type or defindex."); 197 | } 198 | 199 | private void HandleAddCommand(string command) 200 | { 201 | var data = command.Split(' '); 202 | string typeToAdd; 203 | 204 | bool subCmdOk = GetSubCommand (data, out typeToAdd); 205 | 206 | if (!subCmdOk) 207 | return; 208 | 209 | uint amount = GetAddAmount (data); 210 | 211 | // if user supplies the defindex directly use it to add. 212 | int defindex; 213 | if (int.TryParse(typeToAdd, out defindex)) 214 | { 215 | Trade.AddAllItemsByDefindex(defindex, amount); 216 | return; 217 | } 218 | 219 | switch (typeToAdd) 220 | { 221 | case AddMetalSubCmd: 222 | AddItemsByCraftType("craft_bar", amount); 223 | break; 224 | case AddWepsSubCmd: 225 | AddItemsByCraftType("weapon", amount); 226 | break; 227 | case AddCratesSubCmd: 228 | // data[3] is the optional series number 229 | if (!String.IsNullOrEmpty(data[3])) 230 | AddCrateBySeries(data[3], amount); 231 | else 232 | AddItemsByCraftType("supply_crate", amount); 233 | break; 234 | case AddAllSubCmd: 235 | AddAllItems(); 236 | break; 237 | default: 238 | AddItemsByCraftType(typeToAdd, amount); 239 | break; 240 | } 241 | } 242 | 243 | 244 | 245 | private void HandleRemoveCommand(string command) 246 | { 247 | var data = command.Split(' '); 248 | 249 | string subCommand; 250 | 251 | bool subCmdOk = GetSubCommand(data, out subCommand); 252 | 253 | // were dumb right now... just remove everything. 254 | Trade.RemoveAllItems(); 255 | 256 | if (!subCmdOk) 257 | return; 258 | } 259 | 260 | 261 | private void AddItemsByCraftType(string typeToAdd, uint amount) 262 | { 263 | var items = Trade.CurrentSchema.GetItemsByCraftingMaterial(typeToAdd); 264 | 265 | uint added = 0; 266 | 267 | foreach (var item in items) 268 | { 269 | added += Trade.AddAllItemsByDefindex(item.Defindex, amount); 270 | 271 | // if bulk adding something that has a lot of unique 272 | // defindex (weapons) we may over add so limit here also 273 | if (amount > 0 && added >= amount) 274 | return; 275 | } 276 | } 277 | 278 | private void AddAllItems() 279 | { 280 | var items = Trade.CurrentSchema.GetItems(); 281 | 282 | foreach (var item in items) 283 | { 284 | Trade.AddAllItemsByDefindex(item.Defindex, 0); 285 | } 286 | } 287 | 288 | private void AddCrateBySeries(string series, uint amount) 289 | { 290 | int ser; 291 | bool parsed = int.TryParse(series, out ser); 292 | 293 | if (!parsed) 294 | return; 295 | 296 | var l = Trade.CurrentSchema.GetItemsByCraftingMaterial("supply_crate"); 297 | 298 | 299 | List invItems = new List(); 300 | 301 | foreach (var schemaItem in l) 302 | { 303 | ushort defindex = schemaItem.Defindex; 304 | invItems.AddRange(Trade.MyInventory.GetItemsByDefindex(defindex)); 305 | } 306 | 307 | uint added = 0; 308 | 309 | foreach (var item in invItems) 310 | { 311 | int crateNum = 0; 312 | for (int count = 0; count < item.Attributes.Length; count++) 313 | { 314 | // FloatValue will give you the crate's series number 315 | crateNum = (int) item.Attributes[count].FloatValue; 316 | 317 | if (crateNum == ser) 318 | { 319 | bool ok = Trade.AddItem(item.Id); 320 | 321 | if (ok) 322 | added++; 323 | 324 | // if bulk adding something that has a lot of unique 325 | // defindex (weapons) we may over add so limit here also 326 | if (amount > 0 && added >= amount) 327 | return; 328 | } 329 | } 330 | } 331 | } 332 | 333 | bool GetSubCommand (string[] data, out string subCommand) 334 | { 335 | if (data.Length < 2) 336 | { 337 | SendTradeMessage("No parameter for cmd"); 338 | subCommand = null; 339 | return false; 340 | } 341 | 342 | if (String.IsNullOrEmpty (data [1])) 343 | { 344 | SendTradeMessage("No parameter for cmd"); 345 | subCommand = null; 346 | return false; 347 | } 348 | 349 | subCommand = data [1]; 350 | 351 | return true; 352 | } 353 | 354 | static uint GetAddAmount (string[] data) 355 | { 356 | uint amount = 0; 357 | 358 | if (data.Length > 2) 359 | { 360 | // get the optional amount parameter 361 | if (!String.IsNullOrEmpty (data [2])) 362 | { 363 | uint.TryParse (data [2], out amount); 364 | } 365 | } 366 | 367 | return amount; 368 | } 369 | } 370 | } -------------------------------------------------------------------------------- /SteamBot/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("SteamBot")] 4 | [assembly: AssemblyDescription("SteamBot is a bot for the purpose of interacting with Steam Chat and Steam Trade.")] 5 | [assembly: AssemblyConfiguration("")] 6 | [assembly: AssemblyProduct("SteamBot")] 7 | [assembly: AssemblyCopyright("(C) 2012 SteamBot Contributors")] 8 | [assembly: AssemblyTrademark("")] 9 | [assembly: AssemblyCulture("")] 10 | 11 | 12 | // "{Major}.{Minor}.{Build}.*" will automatically update the revision. 13 | // SteamBot uses Semantic Versioning (http://semver.org/) 14 | [assembly: AssemblyVersion("0.1.1.*")] 15 | 16 | -------------------------------------------------------------------------------- /SteamBot/BotInfo.cs: -------------------------------------------------------------------------------- 1 | namespace SteamBot 2 | { 3 | public class BotFile 4 | { 5 | public ulong[] Admins { get; set; } 6 | public BotInfo[] Bots { get; set; } 7 | public string ApiKey { get; set; } 8 | } 9 | 10 | public class BotInfo 11 | { 12 | public string Username { get; set; } 13 | public string Password { get; set; } 14 | public string DisplayName { get; set; } 15 | public string ChatResponse { get; set; } 16 | public ulong[] Admins; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SteamBot/BotManagerInterpreter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace SteamBot 6 | { 7 | /// 8 | /// A interpreter for the bot manager so a user can control bots. 9 | /// 10 | /// 11 | /// There is currently a small set of commands this can interpret. They 12 | /// are limited by the functionality the class 13 | /// exposes. 14 | /// 15 | public class BotManagerInterpreter 16 | { 17 | private readonly BotManager manager; 18 | private CommandSet p; 19 | private string start = String.Empty; 20 | private string stop = String.Empty; 21 | private bool showHelp; 22 | private bool clearConsole; 23 | 24 | public BotManagerInterpreter(BotManager manager) 25 | { 26 | this.manager = manager; 27 | p = new CommandSet 28 | { 29 | new BotManagerOption("stop", "stop (X) where X = the username or index of the configured bot", 30 | s => stop = s), 31 | new BotManagerOption("start", "start (X) where X = the username or index of the configured bot", 32 | s => start = s), 33 | new BotManagerOption("help", "shows this help text", s => showHelp = s != null), 34 | new BotManagerOption("show", 35 | "show (x) where x is one of the following: index, \"bots\", or empty", 36 | param => ShowCommand(param)), 37 | new BotManagerOption("clear", "clears this console", s => clearConsole = s != null), 38 | new BotManagerOption("auth", "auth (X)=(Y) where X = the username or index of the configured bot and Y = the steamguard code", 39 | AuthSet), 40 | new BotManagerOption("exec", 41 | "exec (X) (Y) where X = the username or index of the bot and Y = your custom command to execute", 42 | ExecCommand), 43 | new BotManagerOption("input", 44 | "input (X) (Y) where X = the username or index of the bot and Y = your input", 45 | InputCommand) 46 | }; 47 | } 48 | 49 | void AuthSet(string auth) 50 | { 51 | string[] xy = auth.Split('='); 52 | 53 | if (xy.Length == 2) 54 | { 55 | int index; 56 | string code = xy[1].Trim(); 57 | 58 | if (int.TryParse(xy[0], out index) && (index < manager.ConfigObject.Bots.Length)) 59 | { 60 | Console.WriteLine("Authing bot with '" + code + "'"); 61 | manager.AuthBot(index, code); 62 | } 63 | else if (!String.IsNullOrEmpty(xy[0])) 64 | { 65 | for (index = 0; index < manager.ConfigObject.Bots.Length; index++) 66 | { 67 | if (manager.ConfigObject.Bots[index].Username.Equals(xy[0], StringComparison.CurrentCultureIgnoreCase)) 68 | { 69 | Console.WriteLine("Authing bot with '" + code + "'"); 70 | manager.AuthBot(index, code); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | /// 78 | /// This interprets the given command string. 79 | /// 80 | /// The entire command string. 81 | public void CommandInterpreter(string command) 82 | { 83 | showHelp = false; 84 | start = null; 85 | stop = null; 86 | 87 | p.Parse(command); 88 | 89 | if (showHelp) 90 | { 91 | Console.WriteLine(""); 92 | p.WriteOptionDescriptions(Console.Out); 93 | } 94 | 95 | if (!String.IsNullOrEmpty(stop)) 96 | { 97 | int index; 98 | if (int.TryParse(stop, out index) && (index < manager.ConfigObject.Bots.Length)) 99 | { 100 | manager.StopBot(index); 101 | } 102 | else 103 | { 104 | manager.StopBot(stop); 105 | } 106 | } 107 | 108 | if (!String.IsNullOrEmpty(start)) 109 | { 110 | int index; 111 | if (int.TryParse(start, out index) && (index < manager.ConfigObject.Bots.Length)) 112 | { 113 | manager.StartBot(index); 114 | } 115 | else 116 | { 117 | manager.StartBot(start); 118 | } 119 | } 120 | 121 | if (clearConsole) 122 | { 123 | clearConsole = false; 124 | Console.Clear(); 125 | } 126 | } 127 | 128 | private void ShowCommand(string param) 129 | { 130 | param = param.Trim(); 131 | 132 | int i; 133 | if (int.TryParse(param, out i)) 134 | { 135 | // spit out the bots config at index. 136 | if (manager.ConfigObject.Bots.Length > i) 137 | { 138 | Console.WriteLine(); 139 | Console.WriteLine(manager.ConfigObject.Bots[i].ToString()); 140 | } 141 | } 142 | else if (!String.IsNullOrEmpty(param)) 143 | { 144 | if (param.Equals("bots")) 145 | { 146 | // print out the config.Bots array 147 | foreach (var b in manager.ConfigObject.Bots) 148 | { 149 | Console.WriteLine(); 150 | Console.WriteLine(b.ToString()); 151 | Console.WriteLine(); 152 | } 153 | } 154 | } 155 | else 156 | { 157 | // print out entire config. 158 | // the bots array does not get printed. 159 | Console.WriteLine(); 160 | Console.WriteLine(manager.ConfigObject.ToString()); 161 | } 162 | } 163 | 164 | private void ExecCommand(string cmd) 165 | { 166 | cmd = cmd.Trim(); 167 | 168 | var cs = cmd.Split(' '); 169 | 170 | if (cs.Length < 2) 171 | { 172 | Console.WriteLine("Error: No command given to be executed."); 173 | return; 174 | } 175 | 176 | // Take the rest of the input as is 177 | var command = cmd.Remove(0, cs[0].Length + 1); 178 | 179 | int index; 180 | // Try index first then search usernames 181 | if (int.TryParse(cs[0], out index) && (index < manager.ConfigObject.Bots.Length)) 182 | { 183 | if (manager.ConfigObject.Bots.Length > index) 184 | { 185 | manager.SendCommand(index, command); 186 | return; 187 | } 188 | } 189 | else if (!String.IsNullOrEmpty(cs[0])) 190 | { 191 | for (index = 0; index < manager.ConfigObject.Bots.Length; index++) 192 | { 193 | if (manager.ConfigObject.Bots[index].Username.Equals(cs[0], StringComparison.CurrentCultureIgnoreCase)) 194 | { 195 | manager.SendCommand(index, command); 196 | return; 197 | } 198 | } 199 | } 200 | // Print error 201 | Console.WriteLine("Error: Bot " + cs[0] + " not found."); 202 | } 203 | 204 | private void InputCommand(string inpt) 205 | { 206 | inpt = inpt.Trim(); 207 | 208 | var cs = inpt.Split(' '); 209 | 210 | if (cs.Length < 2) 211 | { 212 | Console.WriteLine("Error: No input given."); 213 | return; 214 | } 215 | 216 | // Take the rest of the input as is 217 | var input = inpt.Remove(0, cs[0].Length + 1); 218 | 219 | int index; 220 | // Try index first then search usernames 221 | if (int.TryParse(cs[0], out index) && (index < manager.ConfigObject.Bots.Length)) 222 | { 223 | if (manager.ConfigObject.Bots.Length > index) 224 | { 225 | manager.SendInput(index, input); 226 | return; 227 | } 228 | } 229 | else if (!String.IsNullOrEmpty(cs[0])) 230 | { 231 | for (index = 0; index < manager.ConfigObject.Bots.Length; index++) 232 | { 233 | if (manager.ConfigObject.Bots[index].Username.Equals(cs[0], StringComparison.CurrentCultureIgnoreCase)) 234 | { 235 | manager.SendInput(index, input); 236 | return; 237 | } 238 | } 239 | } 240 | // Print error 241 | Console.WriteLine("Error: Bot " + cs[0] + " not found."); 242 | } 243 | 244 | #region Nested Options classes 245 | // these are very much like the NDesk.Options but without the 246 | // maturity, features or need for command seprators like "-" or "/" 247 | 248 | private class BotManagerOption 249 | { 250 | public string Name { get; set; } 251 | public string Help { get; set; } 252 | public Action Func { get; set; } 253 | 254 | public BotManagerOption(string name, string help, Action func) 255 | { 256 | Name = name; 257 | Help = help; 258 | Func = func; 259 | } 260 | } 261 | 262 | private class CommandSet : KeyedCollection 263 | { 264 | protected override string GetKeyForItem(BotManagerOption item) 265 | { 266 | return item.Name; 267 | } 268 | 269 | public void Parse(string commandLine) 270 | { 271 | var c = commandLine.Trim(); 272 | 273 | var cs = c.Split(' '); 274 | 275 | foreach (var option in this) 276 | { 277 | if (cs[0].Equals(option.Name, StringComparison.CurrentCultureIgnoreCase)) 278 | { 279 | if (cs.Length > 2) 280 | { 281 | option.Func(c.Remove(0, cs[0].Length + 1)); 282 | } 283 | else if (cs.Length > 1) 284 | { 285 | option.Func(cs[1]); 286 | } 287 | else 288 | { 289 | option.Func(String.Empty); 290 | } 291 | } 292 | } 293 | } 294 | 295 | public void WriteOptionDescriptions(TextWriter o) 296 | { 297 | foreach (BotManagerOption p in this) 298 | { 299 | o.Write('\t'); 300 | o.WriteLine(p.Name + '\t' + p.Help); 301 | } 302 | } 303 | } 304 | 305 | #endregion Nested Options classes 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /SteamBot/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace SteamBot 12 | { 13 | public class Configuration 14 | { 15 | private class JsonToSteamID : JsonConverter 16 | { 17 | static Regex Steam2Regex = new Regex( 18 | @"STEAM_(?[0-5]):(?[0-1]):(?\d+)", 19 | RegexOptions.Compiled | RegexOptions.IgnoreCase); 20 | public override bool CanConvert(Type objectType) 21 | { 22 | return objectType == typeof(IEnumerable); 23 | } 24 | 25 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 26 | { 27 | JArray array = JArray.Load(reader); 28 | List ret = new List(); 29 | foreach (JToken id in array) 30 | { 31 | string sID = (string)id; 32 | if (Steam2Regex.IsMatch(sID)) 33 | ret.Add(new SteamKit2.SteamID(sID)); 34 | else 35 | ret.Add(new SteamKit2.SteamID((ulong)id)); 36 | } 37 | return ret; 38 | } 39 | 40 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | } 45 | 46 | public static Configuration LoadConfiguration (string filename) 47 | { 48 | TextReader reader = new StreamReader(filename); 49 | string json = reader.ReadToEnd(); 50 | reader.Close(); 51 | 52 | Configuration config = JsonConvert.DeserializeObject(json); 53 | 54 | config.Admins = config.Admins ?? new SteamKit2.SteamID[0]; 55 | 56 | // merge bot-specific admins with global admins 57 | foreach (BotInfo bot in config.Bots) 58 | { 59 | if (bot.Admins == null) 60 | bot.Admins = config.Admins; 61 | else 62 | bot.Admins = bot.Admins.Concat(config.Admins); 63 | } 64 | 65 | return config; 66 | } 67 | 68 | #region Top-level config properties 69 | 70 | /// 71 | /// Gets or sets the admins. 72 | /// 73 | /// 74 | /// An array of Steam Profile IDs (64 bit IDs) of the users that are an 75 | /// Admin of your bot(s). Each Profile ID should be a string in quotes 76 | /// and separated by a comma. These admins are global to all bots 77 | /// listed in the Bots array. 78 | /// 79 | [JsonConverter(typeof(JsonToSteamID))] 80 | public IEnumerable Admins { get; set; } 81 | 82 | /// 83 | /// Gets or sets the bots array. 84 | /// 85 | /// 86 | /// The Bots object is an array of BotInfo objects containing 87 | /// information about each individual bot you will be running. 88 | /// 89 | public BotInfo[] Bots { get; set; } 90 | 91 | /// 92 | /// Gets or sets YOUR API key. 93 | /// 94 | /// 95 | /// The API key you have been assigned by Valve. If you do not have 96 | /// one, it can be requested from Value at their Web API Key page. This 97 | /// is required and the bot(s) will not work without an API Key. 98 | /// 99 | public string ApiKey { get; set; } 100 | 101 | /// 102 | /// Gets or sets the main log file name. 103 | /// 104 | public string MainLog { get; set; } 105 | 106 | /// 107 | /// Gets or sets a value indicating whether to use separate processes. 108 | /// 109 | /// 110 | /// true if bot manager is to open each bot in it's own process; 111 | /// otherwise, false to open each bot in a separate thread. 112 | /// Default is false. 113 | /// 114 | public bool UseSeparateProcesses { get; set; } 115 | 116 | /// 117 | /// Gets or sets a value indicating whether to auto start all bots. 118 | /// 119 | /// 120 | /// true to make the bots start on program load; otherwise, 121 | /// false to not start them. 122 | /// 123 | public bool AutoStartAllBots { get; set; } 124 | 125 | #endregion Top-level config properties 126 | 127 | /// 128 | /// Returns a that represents this instance. 129 | /// 130 | /// 131 | /// A that represents this instance. 132 | /// 133 | public override string ToString() 134 | { 135 | StringBuilder sb = new StringBuilder(); 136 | var fields = this.GetType().GetProperties(); 137 | 138 | foreach (var propInfo in fields) 139 | { 140 | sb.AppendFormat("{0} = {1}" + Environment.NewLine, 141 | propInfo.Name, 142 | propInfo.GetValue(this, null)); 143 | } 144 | 145 | return sb.ToString(); 146 | } 147 | 148 | public class BotInfo 149 | { 150 | public string Username { get; set; } 151 | public string Password { get; set; } 152 | public string ApiKey { get; set; } 153 | public string DisplayName { get; set; } 154 | public string ChatResponse { get; set; } 155 | public string LogFile { get; set; } 156 | public string BotControlClass { get; set; } 157 | public int MaximumTradeTime { get; set; } 158 | public int MaximumActionGap { get; set; } 159 | public string DisplayNamePrefix { get; set; } 160 | public int TradePollingInterval { get; set; } 161 | public int TradeOfferPollingIntervalSecs { get; set; } 162 | public string ConsoleLogLevel { get; set; } 163 | public string FileLogLevel { get; set; } 164 | [JsonConverter(typeof(JsonToSteamID))] 165 | public IEnumerable Admins { get; set; } 166 | public string SchemaLang { get; set; } 167 | 168 | // Depreciated configuration options 169 | public string LogLevel { get; set; } 170 | 171 | /// 172 | /// Gets or sets a value indicating whether to auto start this bot. 173 | /// 174 | /// 175 | /// true to make the bot start on program load. 176 | /// 177 | /// 178 | /// If is true, 179 | /// then this property has no effect and is ignored. 180 | /// 181 | [JsonProperty (Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Populate)] 182 | [DefaultValue (true)] 183 | public bool AutoStart { get; set; } 184 | 185 | public override string ToString() 186 | { 187 | StringBuilder sb = new StringBuilder(); 188 | var fields = this.GetType().GetProperties(); 189 | 190 | foreach (var propInfo in fields) 191 | { 192 | sb.AppendFormat("{0} = {1}" + Environment.NewLine, 193 | propInfo.Name, 194 | propInfo.GetValue(this, null)); 195 | } 196 | 197 | return sb.ToString(); 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /SteamBot/ExampleBot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 10.0.0 7 | 2.0 8 | {E81DED36-EDF5-41A5-8666-A3A0C581762F} 9 | Exe 10 | SteamBot 11 | SteamBot 12 | ..\ 13 | true 14 | v4.5 15 | 16 | 17 | 18 | True 19 | full 20 | false 21 | ..\Bin\Debug 22 | DEBUG; 23 | prompt 24 | 4 25 | AnyCPU 26 | True 27 | false 28 | 5 29 | 30 | 31 | full 32 | true 33 | ..\Bin\Release 34 | prompt 35 | 4 36 | AnyCPU 37 | True 38 | false 39 | true 40 | 41 | 42 | 43 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 44 | True 45 | 46 | 47 | False 48 | ..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll 49 | 50 | 51 | ..\packages\SteamAuth.2.0.0\lib\net45\SteamAuth.dll 52 | True 53 | 54 | 55 | ..\packages\SteamKit2.1.8.1\lib\net45\SteamKit2.dll 56 | True 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | False 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | {6CEC0333-81EB-40EE-85D1-941363626FC7} 102 | SteamTrade 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /SteamBot/ExampleBot.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | CSharp50 -------------------------------------------------------------------------------- /SteamBot/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace SteamBot 6 | { 7 | public class Log : IDisposable 8 | { 9 | public enum LogLevel 10 | { 11 | Debug, 12 | Info, 13 | Success, 14 | Warn, 15 | Error, 16 | Interface, // if the user needs to input something 17 | Nothing // not recommended; it basically silences 18 | // the console output because nothing is 19 | // greater than it. even if the bot needs 20 | // input, it won't be shown in the console. 21 | } 22 | 23 | 24 | protected StreamWriter _FileStream; 25 | protected string _botName; 26 | private bool disposed; 27 | public LogLevel OutputLevel; 28 | public LogLevel FileLogLevel; 29 | public ConsoleColor DefaultConsoleColor = ConsoleColor.White; 30 | public bool ShowBotName { get; set; } 31 | 32 | public Log(string logFile, string botName = "", LogLevel consoleLogLevel = LogLevel.Info, LogLevel fileLogLevel = LogLevel.Info) 33 | { 34 | Directory.CreateDirectory(Path.Combine(System.Windows.Forms.Application.StartupPath, "logs")); 35 | _FileStream = File.AppendText (Path.Combine("logs",logFile)); 36 | _FileStream.AutoFlush = true; 37 | _botName = botName; 38 | OutputLevel = consoleLogLevel; 39 | FileLogLevel = fileLogLevel; 40 | Console.ForegroundColor = DefaultConsoleColor; 41 | ShowBotName = true; 42 | } 43 | 44 | ~Log() 45 | { 46 | Dispose(false); 47 | } 48 | 49 | // This outputs a log entry of the level info. 50 | public void Info(string data, params object[] formatParams) 51 | { 52 | _OutputLine(LogLevel.Info, data, formatParams); 53 | } 54 | 55 | // This outputs a log entry of the level debug. 56 | public void Debug(string data, params object[] formatParams) 57 | { 58 | _OutputLine(LogLevel.Debug, data, formatParams); 59 | } 60 | 61 | // This outputs a log entry of the level success. 62 | public void Success(string data, params object[] formatParams) 63 | { 64 | _OutputLine(LogLevel.Success, data, formatParams); 65 | } 66 | 67 | // This outputs a log entry of the level warn. 68 | public void Warn(string data, params object[] formatParams) 69 | { 70 | _OutputLine(LogLevel.Warn, data, formatParams); 71 | } 72 | 73 | // This outputs a log entry of the level error. 74 | public void Error(string data, params object[] formatParams) 75 | { 76 | _OutputLine(LogLevel.Error, data, formatParams); 77 | } 78 | 79 | // This outputs a log entry of the level interface; 80 | // normally, this means that some sort of user interaction 81 | // is required. 82 | public void Interface(string data, params object[] formatParams) 83 | { 84 | _OutputLine(LogLevel.Interface, data, formatParams); 85 | } 86 | 87 | // Outputs a line to both the log and the console, if 88 | // applicable. 89 | protected void _OutputLine(LogLevel level, string line, params object[] formatParams) 90 | { 91 | if (disposed) 92 | return; 93 | string formattedString = String.Format( 94 | "[{0}{1}] {2}: {3}", 95 | GetLogBotName(), 96 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), 97 | _LogLevel(level).ToUpper(), (formatParams != null && formatParams.Any() ? String.Format(line, formatParams) : line) 98 | ); 99 | 100 | if(level >= FileLogLevel) 101 | { 102 | _FileStream.WriteLine(formattedString); 103 | } 104 | if(level >= OutputLevel) 105 | { 106 | _OutputLineToConsole(level, formattedString); 107 | } 108 | } 109 | 110 | private string GetLogBotName() 111 | { 112 | if(_botName == null) 113 | { 114 | return "(System) "; 115 | } 116 | else if(ShowBotName) 117 | { 118 | return _botName + " "; 119 | } 120 | return ""; 121 | } 122 | 123 | // Outputs a line to the console, with the correct color 124 | // formatting. 125 | protected void _OutputLineToConsole (LogLevel level, string line) 126 | { 127 | Console.ForegroundColor = _LogColor (level); 128 | Console.WriteLine (line); 129 | Console.ForegroundColor = DefaultConsoleColor; 130 | } 131 | 132 | // Determine the string equivalent of the LogLevel. 133 | protected string _LogLevel (LogLevel level) 134 | { 135 | switch (level) 136 | { 137 | case LogLevel.Info: 138 | return "info"; 139 | case LogLevel.Debug: 140 | return "debug"; 141 | case LogLevel.Success: 142 | return "success"; 143 | case LogLevel.Warn: 144 | return "warn"; 145 | case LogLevel.Error: 146 | return "error"; 147 | case LogLevel.Interface: 148 | return "interface"; 149 | case LogLevel.Nothing: 150 | return "nothing"; 151 | default: 152 | return "undef"; 153 | } 154 | } 155 | 156 | // Determine the color to be used when outputting to the 157 | // console. 158 | protected ConsoleColor _LogColor (LogLevel level) 159 | { 160 | switch (level) 161 | { 162 | case LogLevel.Info: 163 | case LogLevel.Debug: 164 | return ConsoleColor.White; 165 | case LogLevel.Success: 166 | return ConsoleColor.Green; 167 | case LogLevel.Warn: 168 | return ConsoleColor.Yellow; 169 | case LogLevel.Error: 170 | return ConsoleColor.Red; 171 | case LogLevel.Interface: 172 | return ConsoleColor.DarkCyan; 173 | default: 174 | return DefaultConsoleColor; 175 | } 176 | } 177 | 178 | private void Dispose(bool disposing) 179 | { 180 | if (disposed) 181 | return; 182 | if (disposing) 183 | _FileStream.Dispose(); 184 | disposed = true; 185 | } 186 | 187 | public void Dispose() 188 | { 189 | Dispose(true); 190 | GC.SuppressFinalize(this); 191 | } 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /SteamBot/Notifications.cs: -------------------------------------------------------------------------------- 1 | using SteamKit2; 2 | using SteamKit2.Internal; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | 6 | namespace SteamBot 7 | { 8 | public class SteamNotifications : ClientMsgHandler 9 | { 10 | public class NotificationCallback : CallbackMsg 11 | { 12 | public ReadOnlyCollection Notifications { get; private set; } 13 | 14 | internal NotificationCallback(CMsgClientUserNotifications msg) 15 | { 16 | var list = msg.notifications 17 | .Select(n => new Notification(n)) 18 | .ToList(); 19 | 20 | this.Notifications = new ReadOnlyCollection(list); 21 | } 22 | 23 | public sealed class Notification 24 | { 25 | internal Notification(CMsgClientUserNotifications.Notification notification) 26 | { 27 | Count = notification.count; 28 | UserNotificationType = (UserNotificationType)notification.user_notification_type; 29 | } 30 | 31 | public uint Count { get; private set; } 32 | 33 | public UserNotificationType UserNotificationType { get; private set; } 34 | } 35 | 36 | public enum UserNotificationType 37 | { 38 | TradeOffer = 1, 39 | Unknown 40 | } 41 | } 42 | 43 | public class CommentNotificationCallback : CallbackMsg 44 | { 45 | public CommentNotification CommentNotifications { get; private set; } 46 | 47 | internal CommentNotificationCallback(CMsgClientCommentNotifications msg) 48 | { 49 | CommentNotifications = new CommentNotification(msg); 50 | } 51 | 52 | public sealed class CommentNotification 53 | { 54 | internal CommentNotification(CMsgClientCommentNotifications msg) 55 | { 56 | CountNewComments = msg.count_new_comments; 57 | CountNewCommentsOwner = msg.count_new_comments_owner; 58 | CountNewCommentsSubscriptions = msg.count_new_comments_subscriptions; 59 | } 60 | 61 | public uint CountNewComments { get; private set; } 62 | 63 | public uint CountNewCommentsOwner { get; private set; } 64 | 65 | public uint CountNewCommentsSubscriptions { get; private set; } 66 | } 67 | } 68 | 69 | /// 70 | /// Request to see if the client user has any comment notifications 71 | /// 72 | public void RequestCommentNotifications() 73 | { 74 | var clientRequestCommentNotifications = 75 | new ClientMsgProtobuf(EMsg.ClientRequestCommentNotifications); 76 | 77 | Client.Send(clientRequestCommentNotifications); 78 | } 79 | 80 | /// 81 | /// Request to see if the client user has any notifications. 82 | /// 83 | public void RequestNotifications() 84 | { 85 | var requestItemAnnouncements = 86 | new ClientMsgProtobuf(EMsg.ClientRequestItemAnnouncements); 87 | Client.Send(requestItemAnnouncements); 88 | } 89 | 90 | public override void HandleMsg(IPacketMsg packetMsg) 91 | { 92 | switch (packetMsg.MsgType) 93 | { 94 | case EMsg.ClientNewLoginKey: 95 | HandleClientNewLoginKey(packetMsg); 96 | break; 97 | 98 | case EMsg.ClientUserNotifications: 99 | HandleClientUserNotifications(packetMsg); 100 | break; 101 | 102 | case EMsg.ClientCommentNotifications: 103 | HandleClientCommentNotifications(packetMsg); 104 | break; 105 | } 106 | } 107 | 108 | private void HandleClientUserNotifications(IPacketMsg packetMsg) 109 | { 110 | var clientUserNotificationResponse = new ClientMsgProtobuf(packetMsg); 111 | 112 | CMsgClientUserNotifications result = clientUserNotificationResponse.Body; 113 | 114 | Client.PostCallback(new NotificationCallback(result)); 115 | } 116 | 117 | private void HandleClientCommentNotifications(IPacketMsg packetMsg) 118 | { 119 | var clientCommentNotifications = new ClientMsgProtobuf(packetMsg); 120 | 121 | CMsgClientCommentNotifications result = clientCommentNotifications.Body; 122 | 123 | Client.PostCallback(new CommentNotificationCallback(result)); 124 | } 125 | 126 | private void HandleClientNewLoginKey(IPacketMsg packetMsg) 127 | { 128 | this.RequestCommentNotifications(); 129 | this.RequestNotifications(); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /SteamBot/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using NDesk.Options; 5 | 6 | namespace SteamBot 7 | { 8 | public class Program 9 | { 10 | private static OptionSet opts = new OptionSet() 11 | { 12 | {"bot=", "launch a configured bot given that bots index in the configuration array.", 13 | b => botIndex = Convert.ToInt32(b) } , 14 | { "help", "shows this help text", p => showHelp = (p != null) } 15 | }; 16 | 17 | private static bool showHelp; 18 | 19 | private static int botIndex = -1; 20 | private static BotManager manager; 21 | private static bool isclosing = false; 22 | 23 | [STAThread] 24 | public static void Main(string[] args) 25 | { 26 | opts.Parse(args); 27 | 28 | if (showHelp) 29 | { 30 | Console.ForegroundColor = ConsoleColor.White; 31 | Console.WriteLine("If no options are given SteamBot defaults to Bot Manager mode."); 32 | opts.WriteOptionDescriptions(Console.Out); 33 | Console.Write("Press Enter to exit..."); 34 | Console.ReadLine(); 35 | return; 36 | } 37 | 38 | if (args.Length == 0) 39 | { 40 | BotManagerMode(); 41 | } 42 | else if (botIndex > -1) 43 | { 44 | BotMode(botIndex); 45 | } 46 | } 47 | 48 | #region SteamBot Operational Modes 49 | 50 | // This mode is to run a single Bot until it's terminated. 51 | private static void BotMode(int botIndex) 52 | { 53 | if (!File.Exists("settings.json")) 54 | { 55 | Console.WriteLine("No settings.json file found."); 56 | return; 57 | } 58 | 59 | Configuration configObject; 60 | try 61 | { 62 | configObject = Configuration.LoadConfiguration("settings.json"); 63 | } 64 | catch (Newtonsoft.Json.JsonReaderException) 65 | { 66 | // handle basic json formatting screwups 67 | Console.WriteLine("settings.json file is corrupt or improperly formatted."); 68 | return; 69 | } 70 | 71 | if (botIndex >= configObject.Bots.Length) 72 | { 73 | Console.WriteLine("Invalid bot index."); 74 | return; 75 | } 76 | 77 | Bot b = new Bot(configObject.Bots[botIndex], configObject.ApiKey, BotManager.UserHandlerCreator, true, true); 78 | Console.Title = "Bot Manager"; 79 | b.StartBot(); 80 | 81 | string AuthSet = "auth"; 82 | string ExecCommand = "exec"; 83 | string InputCommand = "input"; 84 | 85 | // this loop is needed to keep the botmode console alive. 86 | // instead of just sleeping, this loop will handle console input 87 | while (true) 88 | { 89 | string inputText = Console.ReadLine(); 90 | 91 | if (String.IsNullOrEmpty(inputText)) 92 | continue; 93 | 94 | // Small parse for console input 95 | var c = inputText.Trim(); 96 | 97 | var cs = c.Split(' '); 98 | 99 | if (cs.Length > 1) 100 | { 101 | if (cs[0].Equals(AuthSet, StringComparison.CurrentCultureIgnoreCase)) 102 | { 103 | b.AuthCode = cs[1].Trim(); 104 | } 105 | else if (cs[0].Equals(ExecCommand, StringComparison.CurrentCultureIgnoreCase)) 106 | { 107 | b.HandleBotCommand(c.Remove(0, cs[0].Length + 1)); 108 | } 109 | else if (cs[0].Equals(InputCommand, StringComparison.CurrentCultureIgnoreCase)) 110 | { 111 | b.HandleInput(c.Remove(0, cs[0].Length + 1)); 112 | } 113 | } 114 | } 115 | } 116 | 117 | // This mode is to manage child bot processes and take use command line inputs 118 | private static void BotManagerMode() 119 | { 120 | Console.Title = "Bot Manager"; 121 | 122 | manager = new BotManager(); 123 | 124 | var loadedOk = manager.LoadConfiguration("settings.json"); 125 | 126 | if (!loadedOk) 127 | { 128 | Console.WriteLine( 129 | "Configuration file Does not exist or is corrupt. Please rename 'settings-template.json' to 'settings.json' and modify the settings to match your environment"); 130 | Console.Write("Press Enter to exit..."); 131 | Console.ReadLine(); 132 | } 133 | else 134 | { 135 | if (manager.ConfigObject.UseSeparateProcesses) 136 | SetConsoleCtrlHandler(ConsoleCtrlCheck, true); 137 | 138 | if (manager.ConfigObject.AutoStartAllBots) 139 | { 140 | var startedOk = manager.StartBots(); 141 | 142 | if (!startedOk) 143 | { 144 | Console.WriteLine( 145 | "Error starting the bots because either the configuration was bad or because the log file was not opened."); 146 | Console.Write("Press Enter to exit..."); 147 | Console.ReadLine(); 148 | } 149 | } 150 | else 151 | { 152 | foreach (var botInfo in manager.ConfigObject.Bots) 153 | { 154 | if (botInfo.AutoStart) 155 | { 156 | // auto start this particual bot... 157 | manager.StartBot(botInfo.Username); 158 | } 159 | } 160 | } 161 | 162 | Console.WriteLine("Type help for bot manager commands. "); 163 | Console.Write("botmgr > "); 164 | 165 | var bmi = new BotManagerInterpreter(manager); 166 | 167 | // command interpreter loop. 168 | do 169 | { 170 | Console.Write("botmgr > "); 171 | string inputText = Console.ReadLine(); 172 | 173 | if (!String.IsNullOrEmpty(inputText)) 174 | bmi.CommandInterpreter(inputText); 175 | 176 | } while (!isclosing); 177 | } 178 | } 179 | 180 | #endregion Bot Modes 181 | 182 | private static bool ConsoleCtrlCheck(CtrlTypes ctrlType) 183 | { 184 | // Put your own handler here 185 | switch (ctrlType) 186 | { 187 | case CtrlTypes.CTRL_C_EVENT: 188 | case CtrlTypes.CTRL_BREAK_EVENT: 189 | case CtrlTypes.CTRL_CLOSE_EVENT: 190 | case CtrlTypes.CTRL_LOGOFF_EVENT: 191 | case CtrlTypes.CTRL_SHUTDOWN_EVENT: 192 | if (manager != null) 193 | { 194 | manager.StopBots(); 195 | } 196 | isclosing = true; 197 | break; 198 | } 199 | 200 | return true; 201 | } 202 | 203 | #region Console Control Handler Imports 204 | 205 | // Declare the SetConsoleCtrlHandler function 206 | // as external and receiving a delegate. 207 | [DllImport("Kernel32")] 208 | public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add); 209 | 210 | // A delegate type to be used as the handler routine 211 | // for SetConsoleCtrlHandler. 212 | public delegate bool HandlerRoutine(CtrlTypes CtrlType); 213 | 214 | // An enumerated type for the control messages 215 | // sent to the handler routine. 216 | public enum CtrlTypes 217 | { 218 | CTRL_C_EVENT = 0, 219 | CTRL_BREAK_EVENT, 220 | CTRL_CLOSE_EVENT, 221 | CTRL_LOGOFF_EVENT = 5, 222 | CTRL_SHUTDOWN_EVENT 223 | } 224 | 225 | #endregion 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /SteamBot/SimpleUserHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SteamKit2; 3 | using System.Collections.Generic; 4 | using SteamTrade; 5 | using SteamTrade.TradeOffer; 6 | using SteamTrade.TradeWebAPI; 7 | 8 | namespace SteamBot 9 | { 10 | public class SimpleUserHandler : UserHandler 11 | { 12 | public TF2Value AmountAdded; 13 | 14 | public SimpleUserHandler (Bot bot, SteamID sid) : base(bot, sid) {} 15 | 16 | public override bool OnGroupAdd() 17 | { 18 | return false; 19 | } 20 | 21 | public override bool OnFriendAdd () 22 | { 23 | return true; 24 | } 25 | 26 | public override void OnLoginCompleted() 27 | { 28 | } 29 | 30 | public override void OnChatRoomMessage(SteamID chatID, SteamID sender, string message) 31 | { 32 | Log.Info(Bot.SteamFriends.GetFriendPersonaName(sender) + ": " + message); 33 | base.OnChatRoomMessage(chatID, sender, message); 34 | } 35 | 36 | public override void OnFriendRemove () {} 37 | 38 | public override void OnMessage (string message, EChatEntryType type) 39 | { 40 | SendChatMessage(Bot.ChatResponse); 41 | } 42 | 43 | public override bool OnTradeRequest() 44 | { 45 | return true; 46 | } 47 | 48 | public override void OnTradeError (string error) 49 | { 50 | SendChatMessage("Oh, there was an error: {0}.", error); 51 | Log.Warn (error); 52 | } 53 | 54 | public override void OnTradeTimeout () 55 | { 56 | SendChatMessage("Sorry, but you were AFK and the trade was canceled."); 57 | Log.Info ("User was kicked because he was AFK."); 58 | } 59 | 60 | public override void OnTradeInit() 61 | { 62 | SendTradeMessage("Success. Please put up your items."); 63 | } 64 | 65 | public override void OnTradeAddItem (Schema.Item schemaItem, Inventory.Item inventoryItem) {} 66 | 67 | public override void OnTradeRemoveItem (Schema.Item schemaItem, Inventory.Item inventoryItem) {} 68 | 69 | public override void OnTradeMessage (string message) {} 70 | 71 | public override void OnTradeReady (bool ready) 72 | { 73 | if (!ready) 74 | { 75 | Trade.SetReady (false); 76 | } 77 | else 78 | { 79 | if(Validate ()) 80 | { 81 | Trade.SetReady (true); 82 | } 83 | SendTradeMessage("Scrap: {0}", AmountAdded.ScrapTotal); 84 | } 85 | } 86 | 87 | public override void OnTradeAwaitingConfirmation(long tradeOfferID) 88 | { 89 | Log.Warn("Trade ended awaiting confirmation"); 90 | SendChatMessage("Please complete the confirmation to finish the trade"); 91 | } 92 | 93 | public override void OnTradeOfferUpdated(TradeOffer offer) 94 | { 95 | switch (offer.OfferState) 96 | { 97 | case TradeOfferState.TradeOfferStateAccepted: 98 | Log.Info(String.Format("Trade offer {0} has been completed!", offer.TradeOfferId)); 99 | SendChatMessage("Trade completed, thank you!"); 100 | break; 101 | case TradeOfferState.TradeOfferStateActive: 102 | case TradeOfferState.TradeOfferStateNeedsConfirmation: 103 | case TradeOfferState.TradeOfferStateInEscrow: 104 | //Trade is still active but incomplete 105 | break; 106 | case TradeOfferState.TradeOfferStateCountered: 107 | Log.Info(String.Format("Trade offer {0} was countered", offer.TradeOfferId)); 108 | break; 109 | default: 110 | Log.Info(String.Format("Trade offer {0} failed", offer.TradeOfferId)); 111 | break; 112 | } 113 | } 114 | 115 | public override void OnTradeAccept() 116 | { 117 | if (Validate() || IsAdmin) 118 | { 119 | //Even if it is successful, AcceptTrade can fail on 120 | //trades with a lot of items so we use a try-catch 121 | try { 122 | if (Trade.AcceptTrade()) 123 | Log.Success("Trade Accepted!"); 124 | } 125 | catch { 126 | Log.Warn ("The trade might have failed, but we can't be sure."); 127 | } 128 | } 129 | } 130 | 131 | public bool Validate () 132 | { 133 | AmountAdded = TF2Value.Zero; 134 | 135 | List errors = new List (); 136 | 137 | foreach (TradeUserAssets asset in Trade.OtherOfferedItems) 138 | { 139 | var item = Trade.OtherInventory.GetItem(asset.assetid); 140 | if (item.Defindex == 5000) 141 | AmountAdded += TF2Value.Scrap; 142 | else if (item.Defindex == 5001) 143 | AmountAdded += TF2Value.Reclaimed; 144 | else if (item.Defindex == 5002) 145 | AmountAdded += TF2Value.Refined; 146 | else 147 | { 148 | var schemaItem = Trade.CurrentSchema.GetItem (item.Defindex); 149 | errors.Add ("Item " + schemaItem.Name + " is not a metal."); 150 | } 151 | } 152 | 153 | if (AmountAdded == TF2Value.Zero) 154 | { 155 | errors.Add ("You must put up at least 1 scrap."); 156 | } 157 | 158 | // send the errors 159 | if (errors.Count != 0) 160 | SendTradeMessage("There were errors in your trade: "); 161 | foreach (string error in errors) 162 | { 163 | SendTradeMessage(error); 164 | } 165 | 166 | return errors.Count == 0; 167 | } 168 | 169 | } 170 | 171 | } 172 | 173 | -------------------------------------------------------------------------------- /SteamBot/SteamGroups/CMsgGroupInviteAction.cs: -------------------------------------------------------------------------------- 1 | ///SteamKit2.ClanMessages 2 | ///Created by Jacob Douglas (gamemaster1494) 3 | ///Created on 4/17/2013 at 2:19 AM CST 4 | ///(c) Copyright 2013, Nebula Programs 5 | 6 | /// Purpose: This class contains the messages used to Invite friends to clans 7 | /// As well as Decline or Accept clan invites. 8 | 9 | using System.IO; 10 | using SteamKit2; 11 | using SteamKit2.Internal; 12 | 13 | namespace SteamBot.SteamGroups 14 | { 15 | //CMsgInviteUserToClan 16 | 17 | /// 18 | /// Message used to Accept or Decline a group(clan) invite. 19 | /// 20 | public class CMsgGroupInviteAction : ISteamSerializableMessage, ISteamSerializable 21 | { 22 | EMsg ISteamSerializableMessage.GetEMsg() 23 | { 24 | return EMsg.ClientAcknowledgeClanInvite; 25 | } 26 | 27 | public CMsgGroupInviteAction() 28 | { 29 | 30 | } 31 | 32 | /// 33 | /// Group invited to. 34 | /// 35 | public ulong GroupID = 0; 36 | 37 | /// 38 | /// To accept or decline the invite. 39 | /// 40 | public bool AcceptInvite = true; 41 | 42 | void ISteamSerializable.Serialize(Stream stream) 43 | { 44 | try 45 | { 46 | BinaryWriter bw = new BinaryWriter(stream); 47 | bw.Write(GroupID); 48 | bw.Write(AcceptInvite); 49 | }//try 50 | catch 51 | { 52 | throw new IOException(); 53 | }//catch 54 | }//Serialize() 55 | 56 | void ISteamSerializable.Deserialize(Stream stream) 57 | { 58 | try 59 | { 60 | BinaryReader br = new BinaryReader(stream); 61 | GroupID = br.ReadUInt64(); 62 | AcceptInvite = br.ReadBoolean(); 63 | }//try 64 | catch 65 | { 66 | throw new IOException(); 67 | }//catch 68 | }//Deserialize() 69 | }//CMsgClanInviteAction 70 | 71 | } 72 | -------------------------------------------------------------------------------- /SteamBot/SteamGroups/CMsgInviteUserToGroup.cs: -------------------------------------------------------------------------------- 1 | ///SteamKit2.ClanMessages 2 | ///Created by Jacob Douglas (gamemaster1494) 3 | ///Created on 4/17/2013 at 2:19 AM CST 4 | ///(c) Copyright 2013, Nebula Programs 5 | 6 | /// Purpose: This class contains the messages used to Invite friends to clans 7 | /// As well as Decline or Accept clan invites. 8 | 9 | using System.IO; 10 | using SteamKit2; 11 | using SteamKit2.Internal; 12 | 13 | namespace SteamBot.SteamGroups 14 | { 15 | /// 16 | /// Message used to invite a user to a group(clan). 17 | /// 18 | public class CMsgInviteUserToGroup : ISteamSerializableMessage, ISteamSerializable 19 | { 20 | EMsg ISteamSerializableMessage.GetEMsg() 21 | { 22 | return EMsg.ClientInviteUserToClan; 23 | } 24 | 25 | public CMsgInviteUserToGroup() 26 | { 27 | 28 | } 29 | 30 | /// 31 | /// Who is being invited. 32 | /// 33 | public ulong Invitee = 0; 34 | 35 | /// 36 | /// Group to invite to 37 | /// 38 | public ulong GroupID = 0; 39 | 40 | /// 41 | /// Not known yet. All data seen shows this as being true. 42 | /// See what happens if its false? 43 | /// 44 | public bool UnknownInfo = true; 45 | 46 | void ISteamSerializable.Serialize(Stream stream) 47 | { 48 | try 49 | { 50 | BinaryWriter bw = new BinaryWriter(stream); 51 | bw.Write(Invitee); 52 | bw.Write(GroupID); 53 | bw.Write(UnknownInfo); 54 | }//try 55 | catch 56 | { 57 | throw new IOException(); 58 | }//catch 59 | }//Serialize() 60 | 61 | void ISteamSerializable.Deserialize(Stream stream) 62 | { 63 | try 64 | { 65 | BinaryReader br = new BinaryReader(stream); 66 | Invitee = br.ReadUInt64(); 67 | GroupID = br.ReadUInt64(); 68 | UnknownInfo = br.ReadBoolean(); 69 | }//try 70 | catch 71 | { 72 | throw new IOException(); 73 | }//catch 74 | }//Deserialize() 75 | } 76 | } -------------------------------------------------------------------------------- /SteamBot/SteamGuardRequiredEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SteamBot 4 | { 5 | public class SteamGuardRequiredEventArgs : EventArgs 6 | { 7 | /// 8 | /// Set this to return the Steam Guard code to the bot. 9 | /// 10 | public string SteamGuard { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SteamBot/SteamTradeDemoHandler.cs: -------------------------------------------------------------------------------- 1 | using SteamKit2; 2 | using System.Collections.Generic; 3 | using SteamTrade; 4 | using SteamTrade.TradeOffer; 5 | 6 | namespace SteamBot 7 | { 8 | public class SteamTradeDemoHandler : UserHandler 9 | { 10 | // NEW ------------------------------------------------------------------ 11 | private readonly GenericInventory mySteamInventory; 12 | private readonly GenericInventory OtherSteamInventory; 13 | 14 | private bool tested; 15 | // ---------------------------------------------------------------------- 16 | 17 | public SteamTradeDemoHandler(Bot bot, SteamID sid) : base(bot, sid) 18 | { 19 | mySteamInventory = new GenericInventory(SteamWeb); 20 | OtherSteamInventory = new GenericInventory(SteamWeb); 21 | } 22 | 23 | public override bool OnGroupAdd() 24 | { 25 | return false; 26 | } 27 | 28 | public override bool OnFriendAdd () 29 | { 30 | return true; 31 | } 32 | 33 | public override void OnLoginCompleted() {} 34 | 35 | public override void OnChatRoomMessage(SteamID chatID, SteamID sender, string message) 36 | { 37 | Log.Info(Bot.SteamFriends.GetFriendPersonaName(sender) + ": " + message); 38 | base.OnChatRoomMessage(chatID, sender, message); 39 | } 40 | 41 | public override void OnFriendRemove () {} 42 | 43 | public override void OnMessage (string message, EChatEntryType type) 44 | { 45 | SendChatMessage(Bot.ChatResponse); 46 | } 47 | 48 | public override bool OnTradeRequest() 49 | { 50 | return true; 51 | } 52 | 53 | public override void OnTradeError (string error) 54 | { 55 | SendChatMessage("Oh, there was an error: {0}.", error); 56 | Log.Warn (error); 57 | } 58 | 59 | public override void OnTradeTimeout () 60 | { 61 | SendChatMessage("Sorry, but you were AFK and the trade was canceled."); 62 | Log.Info ("User was kicked because he was AFK."); 63 | } 64 | 65 | public override void OnTradeInit() 66 | { 67 | // NEW ------------------------------------------------------------------------------- 68 | List contextId = new List(); 69 | tested = false; 70 | 71 | /************************************************************************************* 72 | * 73 | * SteamInventory AppId = 753 74 | * 75 | * Context Id Description 76 | * 1 Gifts (Games), must be public on steam profile in order to work. 77 | * 6 Trading Cards, Emoticons & Backgrounds. 78 | * 79 | ************************************************************************************/ 80 | 81 | contextId.Add(1); 82 | contextId.Add(6); 83 | 84 | mySteamInventory.load(753, contextId, Bot.SteamClient.SteamID); 85 | OtherSteamInventory.load(753, contextId, OtherSID); 86 | 87 | if (!mySteamInventory.isLoaded | !OtherSteamInventory.isLoaded) 88 | { 89 | SendTradeMessage("Couldn't open an inventory, type 'errors' for more info."); 90 | } 91 | 92 | SendTradeMessage("Type 'test' to start."); 93 | // ----------------------------------------------------------------------------------- 94 | } 95 | 96 | public override void OnTradeAddItem (Schema.Item schemaItem, Inventory.Item inventoryItem) { 97 | // USELESS DEBUG MESSAGES ------------------------------------------------------------------------------- 98 | SendTradeMessage("Object AppID: {0}", inventoryItem.AppId); 99 | SendTradeMessage("Object ContextId: {0}", inventoryItem.ContextId); 100 | 101 | switch (inventoryItem.AppId) 102 | { 103 | case 440: 104 | SendTradeMessage("TF2 Item Added."); 105 | SendTradeMessage("Name: {0}", schemaItem.Name); 106 | SendTradeMessage("Quality: {0}", inventoryItem.Quality); 107 | SendTradeMessage("Level: {0}", inventoryItem.Level); 108 | SendTradeMessage("Craftable: {0}", (inventoryItem.IsNotCraftable ? "No" : "Yes")); 109 | break; 110 | 111 | case 753: 112 | GenericInventory.ItemDescription tmpDescription = OtherSteamInventory.getDescription(inventoryItem.Id); 113 | SendTradeMessage("Steam Inventory Item Added."); 114 | SendTradeMessage("Type: {0}", tmpDescription.type); 115 | SendTradeMessage("Marketable: {0}", (tmpDescription.marketable ? "Yes" : "No")); 116 | break; 117 | 118 | default: 119 | SendTradeMessage("Unknown item"); 120 | break; 121 | } 122 | // ------------------------------------------------------------------------------------------------------ 123 | } 124 | 125 | public override void OnTradeRemoveItem (Schema.Item schemaItem, Inventory.Item inventoryItem) {} 126 | 127 | public override void OnTradeMessage (string message) { 128 | switch (message.ToLower()) 129 | { 130 | case "errors": 131 | if (OtherSteamInventory.errors.Count > 0) 132 | { 133 | SendTradeMessage("User Errors:"); 134 | foreach (string error in OtherSteamInventory.errors) 135 | { 136 | SendTradeMessage(" * {0}", error); 137 | } 138 | } 139 | 140 | if (mySteamInventory.errors.Count > 0) 141 | { 142 | SendTradeMessage("Bot Errors:"); 143 | foreach (string error in mySteamInventory.errors) 144 | { 145 | SendTradeMessage(" * {0}", error); 146 | } 147 | } 148 | break; 149 | 150 | case "test": 151 | if (tested) 152 | { 153 | foreach (GenericInventory.Item item in mySteamInventory.items.Values) 154 | { 155 | Trade.RemoveItem(item); 156 | } 157 | } 158 | else 159 | { 160 | SendTradeMessage("Items on my bp: {0}", mySteamInventory.items.Count); 161 | foreach (GenericInventory.Item item in mySteamInventory.items.Values) 162 | { 163 | Trade.AddItem(item); 164 | } 165 | } 166 | 167 | tested = !tested; 168 | 169 | break; 170 | 171 | case "remove": 172 | foreach (var item in mySteamInventory.items) 173 | { 174 | Trade.RemoveItem(item.Value.assetid, item.Value.appid, item.Value.contextid); 175 | } 176 | break; 177 | } 178 | } 179 | 180 | public override void OnTradeReady (bool ready) 181 | { 182 | //Because SetReady must use its own version, it's important 183 | //we poll the trade to make sure everything is up-to-date. 184 | Trade.Poll(); 185 | if (!ready) 186 | { 187 | Trade.SetReady (false); 188 | } 189 | else 190 | { 191 | if(Validate () | IsAdmin) 192 | { 193 | Trade.SetReady (true); 194 | } 195 | } 196 | } 197 | 198 | public override void OnTradeOfferUpdated(TradeOffer offer) 199 | { 200 | if(offer.OfferState == TradeOfferState.TradeOfferStateAccepted) 201 | { 202 | Log.Success("Trade Complete."); 203 | } 204 | } 205 | 206 | public override void OnTradeAwaitingConfirmation(long tradeOfferID) 207 | { 208 | Log.Warn("Trade ended awaiting confirmation"); 209 | SendChatMessage("Please complete the confirmation to finish the trade"); 210 | } 211 | 212 | public override void OnTradeAccept() 213 | { 214 | if (Validate() | IsAdmin) 215 | { 216 | //Even if it is successful, AcceptTrade can fail on 217 | //trades with a lot of items so we use a try-catch 218 | try { 219 | Trade.AcceptTrade(); 220 | } 221 | catch { 222 | Log.Warn ("The trade might have failed, but we can't be sure."); 223 | } 224 | 225 | Log.Success ("Trade Complete!"); 226 | } 227 | } 228 | 229 | public bool Validate () 230 | { 231 | List errors = new List (); 232 | errors.Add("This demo is meant to show you how to handle SteamInventory Items. Trade cannot be completed, unless you're an Admin."); 233 | 234 | // send the errors 235 | if (errors.Count != 0) 236 | SendTradeMessage("There were errors in your trade: "); 237 | 238 | foreach (string error in errors) 239 | { 240 | SendTradeMessage(error); 241 | } 242 | 243 | return errors.Count == 0; 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | -------------------------------------------------------------------------------- /SteamBot/TF2GC/Crafting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using SteamKit2.GC; 6 | 7 | namespace SteamBot.TF2GC 8 | { 9 | public enum ECraftingRecipe : short 10 | { 11 | SmeltClassWeapons = 3, 12 | CombineScrap = 4, 13 | CombineReclaimed = 5, 14 | SmeltReclaimed = 22, 15 | SmeltRefined = 23 16 | } 17 | 18 | public static class Crafting 19 | { 20 | /// 21 | /// Crafts the specified items using the best fit recipe. 22 | /// 23 | /// The current Bot 24 | /// A list of ulong Item IDs to craft 25 | /// 26 | /// You must have set the current game to 440 for this to do anything. 27 | /// 28 | public static void CraftItems(Bot bot, params ulong[] items) 29 | { 30 | CraftItems(bot, -2, items); 31 | } 32 | /// 33 | /// Crafts the specified items using the specified recipe. 34 | /// 35 | /// The current Bot 36 | /// The recipe number (unknown atm; -2 is "best fit") 37 | /// A list of ulong Item IDs to craft 38 | /// 39 | /// You must have set the current game to 440 for this to do anything. 40 | /// 41 | public static void CraftItems(Bot bot, ECraftingRecipe recipe, params ulong[] items) 42 | { 43 | CraftItems(bot, (short)recipe, items); 44 | } 45 | public static void CraftItems(Bot bot, short recipe, params ulong[] items) 46 | { 47 | if (bot.CurrentGame != 440) 48 | throw new Exception("SteamBot is not ingame with AppID 440; current AppID is " + bot.CurrentGame); 49 | 50 | var craftMsg = new ClientGCMsg(); 51 | 52 | craftMsg.Body.NumItems = (short)items.Length; 53 | craftMsg.Body.Recipe = recipe; 54 | 55 | foreach (ulong id in items) 56 | craftMsg.Write(id); 57 | 58 | bot.SteamGameCoordinator.Send(craftMsg, 440); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SteamBot/TF2GC/Items.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using SteamKit2.GC; 6 | 7 | namespace SteamBot.TF2GC 8 | { 9 | public static class Items 10 | { 11 | /// 12 | /// Permanently deletes the specified item 13 | /// 14 | /// The current Bot 15 | /// The 64-bit Item ID to delete 16 | /// 17 | /// You must have set the current game to 440 for this to do anything. 18 | /// 19 | public static void DeleteItem(Bot bot, ulong item) 20 | { 21 | if (bot.CurrentGame != 440) 22 | throw new Exception("SteamBot is not ingame with AppID 440; current AppID is " + bot.CurrentGame); 23 | 24 | var deleteMsg = new ClientGCMsg(); 25 | 26 | deleteMsg.Write((ulong)item); 27 | 28 | bot.SteamGameCoordinator.Send(deleteMsg, 440); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SteamBot/TF2GC/MsgCraft.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using SteamKit2; 7 | using SteamKit2.GC; 8 | using SteamKit2.GC.TF2; 9 | using SteamKit2.Internal; 10 | 11 | namespace SteamBot.TF2GC 12 | { 13 | public class MsgCraft : IGCSerializableMessage 14 | { 15 | public ulong[] IdsToSend; 16 | public short Recipe = -2; 17 | public short NumItems = 2; 18 | 19 | public MsgCraft() 20 | { 21 | } 22 | 23 | public bool IsProto 24 | { 25 | get { return false; } 26 | } 27 | 28 | //1002 is the EMsg code for crafting 29 | public uint MsgType 30 | { 31 | get { return 1002; } 32 | } 33 | 34 | public JobID TargetJobID 35 | { 36 | get { return new JobID(); } 37 | set { throw new NotImplementedException(); } 38 | } 39 | 40 | public JobID SourceJobID 41 | { 42 | get { return new JobID(); } 43 | set { throw new NotImplementedException(); } 44 | } 45 | 46 | public byte[] Serialize() 47 | { 48 | List ret = new List(); 49 | ret.AddRange(BitConverter.GetBytes((short)Recipe)); 50 | ret.AddRange(BitConverter.GetBytes((short)NumItems)); 51 | return ret.ToArray(); 52 | } 53 | 54 | public void Serialize(Stream stream) 55 | { 56 | byte[] buf = Serialize(); 57 | stream.Write(buf, 0, buf.Length); 58 | } 59 | 60 | public void Deserialize(Stream stream) 61 | { 62 | 63 | } 64 | 65 | 66 | //1002 is the EMsg code for crafting 67 | public uint GetEMsg() 68 | { 69 | return 1002; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SteamBot/TF2GC/MsgDelete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using SteamKit2; 7 | using SteamKit2.GC; 8 | using SteamKit2.GC.TF2; 9 | using SteamKit2.Internal; 10 | 11 | namespace SteamBot.TF2GC 12 | { 13 | public class MsgDelete : IGCSerializableMessage 14 | { 15 | 16 | public MsgDelete() 17 | { 18 | 19 | } 20 | 21 | public bool IsProto 22 | { 23 | get { return false; } 24 | } 25 | 26 | public uint MsgType 27 | { 28 | get { return 1004; } 29 | } 30 | 31 | public JobID TargetJobID 32 | { 33 | get { return new JobID(); } 34 | set { throw new NotImplementedException(); } 35 | } 36 | 37 | public JobID SourceJobID 38 | { 39 | get { return new JobID(); } 40 | set { throw new NotImplementedException(); } 41 | } 42 | 43 | public byte[] Serialize() 44 | { 45 | List ret = new List(); 46 | return ret.ToArray(); 47 | } 48 | 49 | public void Serialize(Stream stream) 50 | { 51 | byte[] buf = Serialize(); 52 | stream.Write(buf, 0, buf.Length); 53 | } 54 | 55 | public void Deserialize(Stream stream) 56 | { 57 | 58 | } 59 | 60 | public uint GetEMsg() 61 | { 62 | //1004 is the EMsg for item deletion 63 | return 1004; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /SteamBot/TradeOfferUserHandler.cs: -------------------------------------------------------------------------------- 1 | using SteamKit2; 2 | using SteamTrade; 3 | using SteamTrade.TradeOffer; 4 | using System; 5 | using System.Collections.Generic; 6 | using TradeAsset = SteamTrade.TradeOffer.TradeOffer.TradeStatusUser.TradeAsset; 7 | 8 | namespace SteamBot 9 | { 10 | public class TradeOfferUserHandler : UserHandler 11 | { 12 | public TradeOfferUserHandler(Bot bot, SteamID sid) : base(bot, sid) { } 13 | 14 | public override void OnTradeOfferUpdated(TradeOffer offer) 15 | { 16 | if(offer.OfferState == TradeOfferState.TradeOfferStateActive && !offer.IsOurOffer) 17 | { 18 | OnNewTradeOffer(offer); 19 | } 20 | } 21 | 22 | private void OnNewTradeOffer(TradeOffer offer) 23 | { 24 | //receiving a trade offer 25 | if (IsAdmin) 26 | { 27 | //parse inventories of bot and other partner 28 | //either with webapi or generic inventory 29 | //Bot.GetInventory(); 30 | //Bot.GetOtherInventory(OtherSID); 31 | 32 | var myItems = offer.Items.GetMyItems(); 33 | var theirItems = offer.Items.GetTheirItems(); 34 | Log.Info("They want " + myItems.Count + " of my items."); 35 | Log.Info("And I will get " + theirItems.Count + " of their items."); 36 | 37 | //do validation logic etc 38 | if (DummyValidation(myItems, theirItems)) 39 | { 40 | TradeOfferAcceptResponse acceptResp = offer.Accept(); 41 | if (acceptResp.Accepted) 42 | { 43 | Bot.AcceptAllMobileTradeConfirmations(); 44 | Log.Success("Accepted trade offer successfully : Trade ID: " + acceptResp.TradeId); 45 | } 46 | } 47 | else 48 | { 49 | // maybe we want different items or something 50 | 51 | //offer.Items.AddMyItem(0, 0, 0); 52 | //offer.Items.RemoveTheirItem(0, 0, 0); 53 | if (offer.Items.NewVersion) 54 | { 55 | string newOfferId; 56 | if (offer.CounterOffer(out newOfferId)) 57 | { 58 | Bot.AcceptAllMobileTradeConfirmations(); 59 | Log.Success("Counter offered successfully : New Offer ID: " + newOfferId); 60 | } 61 | } 62 | } 63 | } 64 | else 65 | { 66 | //we don't know this user so we can decline 67 | if (offer.Decline()) 68 | { 69 | Log.Info("Declined trade offer : " + offer.TradeOfferId + " from untrusted user " + OtherSID.ConvertToUInt64()); 70 | } 71 | } 72 | } 73 | 74 | public override void OnMessage(string message, EChatEntryType type) 75 | { 76 | if (IsAdmin) 77 | { 78 | //creating a new trade offer 79 | var offer = Bot.NewTradeOffer(OtherSID); 80 | 81 | //offer.Items.AddMyItem(0, 0, 0); 82 | if (offer.Items.NewVersion) 83 | { 84 | string newOfferId; 85 | if (offer.Send(out newOfferId)) 86 | { 87 | Bot.AcceptAllMobileTradeConfirmations(); 88 | Log.Success("Trade offer sent : Offer ID " + newOfferId); 89 | } 90 | } 91 | 92 | //creating a new trade offer with token 93 | var offerWithToken = Bot.NewTradeOffer(OtherSID); 94 | 95 | //offer.Items.AddMyItem(0, 0, 0); 96 | if (offerWithToken.Items.NewVersion) 97 | { 98 | string newOfferId; 99 | // "token" should be replaced with the actual token from the other user 100 | if (offerWithToken.SendWithToken(out newOfferId, "token")) 101 | { 102 | Bot.AcceptAllMobileTradeConfirmations(); 103 | Log.Success("Trade offer sent : Offer ID " + newOfferId); 104 | } 105 | } 106 | } 107 | } 108 | 109 | public override bool OnGroupAdd() { return false; } 110 | 111 | public override bool OnFriendAdd() { return IsAdmin; } 112 | 113 | public override void OnFriendRemove() { } 114 | 115 | public override void OnLoginCompleted() { } 116 | 117 | public override bool OnTradeRequest() { return false; } 118 | 119 | public override void OnTradeError(string error) { } 120 | 121 | public override void OnTradeTimeout() { } 122 | 123 | public override void OnTradeAwaitingConfirmation(long tradeOfferID) { } 124 | 125 | public override void OnTradeInit() { } 126 | 127 | public override void OnTradeAddItem(Schema.Item schemaItem, Inventory.Item inventoryItem) { } 128 | 129 | public override void OnTradeRemoveItem(Schema.Item schemaItem, Inventory.Item inventoryItem) { } 130 | 131 | public override void OnTradeMessage(string message) { } 132 | 133 | public override void OnTradeReady(bool ready) { } 134 | 135 | public override void OnTradeAccept() { } 136 | 137 | private bool DummyValidation(List myAssets, List theirAssets) 138 | { 139 | //compare items etc 140 | if (myAssets.Count == theirAssets.Count) 141 | { 142 | return true; 143 | } 144 | return false; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SteamBot/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SteamBot/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SteamBotUnitTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SteamBotUnitTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SteamBotUnitTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("54039ab1-e82a-4d9f-93f2-1a96e42ec6de")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SteamBotUnitTest/SteamTradeUnitTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DD770440-43F0-4440-BD36-DD91A0EB6842} 8 | Library 9 | Properties 10 | SteamBotUnitTest 11 | SteamBotUnitTest 12 | v4.5.1 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {6cec0333-81eb-40ee-85d1-941363626fc7} 55 | SteamTrade 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /SteamBotUnitTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /SteamTrade/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("SteamTrade")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SteamTrade")] 12 | [assembly: AssemblyCopyright("C) 2012 SteamBot Contributors")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | // SteamTrade uses Semantic Versioning (http://semver.org/) 21 | [assembly: AssemblyVersion("0.1.0.*")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | 29 | -------------------------------------------------------------------------------- /SteamTrade/Exceptions/InventoryFetchException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SteamKit2; 3 | 4 | namespace SteamTrade.Exceptions 5 | { 6 | public class InventoryFetchException : TradeException 7 | { 8 | public InventoryFetchException () 9 | { 10 | } 11 | 12 | /// 13 | /// Gets the Steam identifier that caused the fetch exception. 14 | /// 15 | /// 16 | /// The failing steam identifier. 17 | /// 18 | public SteamID FailingSteamId { get; private set; } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// 24 | /// Steam identifier that caused the fetch exception. 25 | /// 26 | public InventoryFetchException (SteamID steamId) 27 | : base(String.Format("Failed to fetch inventory for: {0}", steamId.ToString())) 28 | { 29 | FailingSteamId = steamId; 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /SteamTrade/Exceptions/TradeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SteamTrade.Exceptions 7 | { 8 | /// 9 | /// A basic exception that occurs in the trading library. 10 | /// 11 | public class TradeException : Exception 12 | { 13 | public TradeException() 14 | { 15 | } 16 | 17 | public TradeException(string message) 18 | : base(message) 19 | { 20 | } 21 | 22 | public TradeException(string message, Exception inner) 23 | : base(message, inner) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SteamTrade/ForeignInventory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace SteamTrade 5 | { 6 | /// 7 | /// This represents an inventory as decoded from the Steam Trade API 8 | /// function of the same name. 9 | /// 10 | /// 11 | /// This class takes the results of the following Trade API call: 12 | /// POST /trade/(steamid)/foreigninventory/sessionid=(trade_session_id)&steamid=(steamid)&appid=(appid)&contextid=(trade contextid) 13 | /// 14 | /// The trade context id is important and only obtainable from being in 15 | /// a trade. 16 | /// 17 | public class ForeignInventory 18 | { 19 | private readonly dynamic rawJson; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// 25 | /// The json returned from the foreigninventory Web API call. 26 | /// 27 | public ForeignInventory (dynamic rawJson) 28 | { 29 | this.rawJson = rawJson; 30 | 31 | if (rawJson.success == "true") 32 | { 33 | InventoryValid = true; 34 | } 35 | } 36 | 37 | /// 38 | /// Gets a value indicating whether the inventory is valid. 39 | /// 40 | /// 41 | /// true if the inventory is valid; otherwise, false. 42 | /// 43 | public bool InventoryValid 44 | { 45 | get; private set; 46 | } 47 | 48 | /// 49 | /// Gets the class id for the given item. 50 | /// 51 | /// The item id. 52 | /// A class ID or 0 if there is an error. 53 | public uint GetClassIdForItemId(ulong itemId) 54 | { 55 | string i = itemId.ToString(CultureInfo.InvariantCulture); 56 | 57 | try 58 | { 59 | return rawJson.rgInventory[i].classid; 60 | } 61 | catch 62 | { 63 | return 0; 64 | } 65 | } 66 | 67 | /// 68 | /// Gets the instance id for given item. 69 | /// 70 | /// The item id. 71 | /// A instance ID or 0 if there is an error. 72 | public ulong GetInstanceIdForItemId(ulong itemId) 73 | { 74 | string i = itemId.ToString(CultureInfo.InvariantCulture); 75 | 76 | try 77 | { 78 | return rawJson.rgInventory[i].instanceid; 79 | } 80 | catch 81 | { 82 | return 0; 83 | } 84 | } 85 | 86 | 87 | /// 88 | /// Gets the defindex for a given Item ID. 89 | /// 90 | /// The item id. 91 | /// A defindex or -1 if there is an error. 92 | public int GetDefIndex(ulong itemId) 93 | { 94 | try 95 | { 96 | uint classId = GetClassIdForItemId(itemId); 97 | ulong iid = GetInstanceIdForItemId(itemId); 98 | 99 | if (classId == 0 || iid == 0) 100 | { 101 | return -1; 102 | } 103 | 104 | // for tf2 the def index is in the app_data section in the 105 | // descriptions object. this may not be the case for all 106 | // games and therefore this may be non-portable. 107 | string index = classId + "_" + iid; 108 | string defIndex = rawJson.rgDescriptions[index].app_data.def_index; 109 | return int.Parse(defIndex); 110 | } 111 | catch 112 | { 113 | return -1; 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SteamTrade/GenericInventory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using SteamKit2; 8 | using SteamTrade.TradeWebAPI; 9 | 10 | namespace SteamTrade 11 | { 12 | 13 | /// 14 | /// Generic Steam Backpack Interface 15 | /// 16 | public class GenericInventory 17 | { 18 | private readonly SteamWeb SteamWeb; 19 | 20 | public GenericInventory(SteamWeb steamWeb) 21 | { 22 | SteamWeb = steamWeb; 23 | } 24 | 25 | public Dictionary items 26 | { 27 | get 28 | { 29 | if (_loadTask == null) 30 | return null; 31 | _loadTask.Wait(); 32 | return _items; 33 | } 34 | } 35 | 36 | public Dictionary descriptions 37 | { 38 | get 39 | { 40 | if (_loadTask == null) 41 | return null; 42 | _loadTask.Wait(); 43 | return _descriptions; 44 | } 45 | } 46 | 47 | public List errors 48 | { 49 | get 50 | { 51 | if (_loadTask == null) 52 | return null; 53 | _loadTask.Wait(); 54 | return _errors; 55 | } 56 | } 57 | 58 | public bool isLoaded = false; 59 | 60 | private Task _loadTask; 61 | private Dictionary _descriptions = new Dictionary(); 62 | private Dictionary _items = new Dictionary(); 63 | private List _errors = new List(); 64 | 65 | public class Item : TradeUserAssets 66 | { 67 | public Item(int appid, long contextid, ulong assetid, string descriptionid, int amount = 1) : base(appid, contextid, assetid, amount) 68 | { 69 | this.descriptionid = descriptionid; 70 | } 71 | 72 | public string descriptionid { get; private set; } 73 | 74 | public override string ToString() 75 | { 76 | return string.Format("id:{0}, appid:{1}, contextid:{2}, amount:{3}, descriptionid:{4}", 77 | assetid, appid, contextid, amount, descriptionid); 78 | } 79 | } 80 | 81 | public class ItemDescription 82 | { 83 | public string name { get; set; } 84 | public string type { get; set; } 85 | public bool tradable { get; set; } 86 | public bool marketable { get; set; } 87 | public string url { get; set; } 88 | public long classid { get; set; } 89 | public long market_fee_app_id { get; set; } 90 | 91 | public Dictionary app_data { get; set; } 92 | 93 | public void debug_app_data() 94 | { 95 | Console.WriteLine("\n\"" + name + "\""); 96 | if (app_data == null) 97 | { 98 | Console.WriteLine("Doesn't have app_data"); 99 | return; 100 | } 101 | 102 | foreach (var value in app_data) 103 | { 104 | Console.WriteLine(string.Format("{0} = {1}", value.Key, value.Value)); 105 | } 106 | Console.WriteLine(""); 107 | } 108 | } 109 | 110 | /// 111 | /// Returns information (such as item name, etc) about the given item. 112 | /// This call can fail, usually when the user's inventory is private. 113 | /// 114 | public ItemDescription getDescription(ulong id) 115 | { 116 | if (_loadTask == null) 117 | return null; 118 | _loadTask.Wait(); 119 | 120 | try 121 | { 122 | return _descriptions[_items[id].descriptionid]; 123 | } 124 | catch 125 | { 126 | return null; 127 | } 128 | } 129 | 130 | public void load(int appid, IEnumerable contextIds, SteamID steamid) 131 | { 132 | List contextIdsCopy = contextIds.ToList(); 133 | _loadTask = Task.Factory.StartNew(() => loadImplementation(appid, contextIdsCopy, steamid)); 134 | } 135 | 136 | public void loadImplementation(int appid, IEnumerable contextIds, SteamID steamid) 137 | { 138 | dynamic invResponse; 139 | isLoaded = false; 140 | Dictionary tmpAppData; 141 | 142 | _items.Clear(); 143 | _descriptions.Clear(); 144 | _errors.Clear(); 145 | 146 | try 147 | { 148 | foreach (long contextId in contextIds) 149 | { 150 | string moreStart = null; 151 | do 152 | { 153 | var data = String.IsNullOrEmpty(moreStart) ? null : new NameValueCollection {{"start", moreStart}}; 154 | string response = SteamWeb.Fetch( 155 | String.Format("http://steamcommunity.com/profiles/{0}/inventory/json/{1}/{2}/", steamid.ConvertToUInt64(), appid, contextId), 156 | "GET", data); 157 | invResponse = JsonConvert.DeserializeObject(response); 158 | 159 | if (invResponse.success == false) 160 | { 161 | _errors.Add("Fail to open backpack: " + invResponse.Error); 162 | continue; 163 | } 164 | 165 | //rgInventory = Items on Steam Inventory 166 | foreach (var item in invResponse.rgInventory) 167 | { 168 | foreach (var itemId in item) 169 | { 170 | ulong id = (ulong) itemId.id; 171 | if (!_items.ContainsKey(id)) 172 | { 173 | string descriptionid = itemId.classid + "_" + itemId.instanceid; 174 | _items.Add((ulong)itemId.id, new Item(appid, contextId, (ulong)itemId.id, descriptionid)); 175 | break; 176 | } 177 | } 178 | } 179 | 180 | // rgDescriptions = Item Schema (sort of) 181 | foreach (var description in invResponse.rgDescriptions) 182 | { 183 | foreach (var class_instance in description) // classid + '_' + instenceid 184 | { 185 | string key = "" + (class_instance.classid ?? '0') + "_" + (class_instance.instanceid ?? '0'); 186 | if (!_descriptions.ContainsKey(key)) 187 | { 188 | if(class_instance.app_data != null) 189 | { 190 | tmpAppData = new Dictionary(); 191 | foreach(var value in class_instance.app_data) 192 | { 193 | tmpAppData.Add("" + value.Name, "" + value.Value); 194 | } 195 | } 196 | else 197 | { 198 | tmpAppData = null; 199 | } 200 | 201 | _descriptions.Add(key, 202 | new ItemDescription() 203 | { 204 | name = class_instance.name, 205 | type = class_instance.type, 206 | marketable = (bool)class_instance.marketable, 207 | tradable = (bool)class_instance.tradable, 208 | classid = long.Parse((string)class_instance.classid), 209 | url = (class_instance.actions != null && class_instance.actions.First["link"] != null ? class_instance.actions.First["link"] : ""), 210 | app_data = tmpAppData, 211 | market_fee_app_id = (class_instance.market_fee_app != null ? class_instance.market_fee_app : 0), 212 | } 213 | ); 214 | break; 215 | } 216 | 217 | } 218 | } 219 | 220 | try 221 | { 222 | moreStart = invResponse.more_start; 223 | } 224 | catch (Exception e) 225 | { 226 | moreStart = null; 227 | } 228 | } while (!String.IsNullOrEmpty(moreStart) && moreStart.ToLower() != "false"); 229 | }//end for (contextId) 230 | }//end try 231 | catch (Exception e) 232 | { 233 | Console.WriteLine(e); 234 | _errors.Add("Exception: " + e.Message); 235 | } 236 | isLoaded = true; 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /SteamTrade/Inventory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using SteamKit2; 6 | 7 | namespace SteamTrade 8 | { 9 | public class Inventory 10 | { 11 | /// 12 | /// Fetches the inventory for the given Steam ID using the Steam API. 13 | /// 14 | /// The give users inventory. 15 | /// Steam identifier. 16 | /// The needed Steam API key. 17 | /// The SteamWeb instance for this Bot 18 | public static Inventory FetchInventory(ulong steamId, string apiKey, SteamWeb steamWeb) 19 | { 20 | int attempts = 1; 21 | InventoryResponse result = null; 22 | while ((result == null || result.result.items == null) && attempts <= 3) 23 | { 24 | var url = "http://api.steampowered.com/IEconItems_440/GetPlayerItems/v0001/?key=" + apiKey + "&steamid=" + steamId; 25 | string response = steamWeb.Fetch(url, "GET", null, false); 26 | result = JsonConvert.DeserializeObject(response); 27 | attempts++; 28 | } 29 | return new Inventory(result.result); 30 | } 31 | 32 | /// 33 | /// Gets the inventory for the given Steam ID using the Steam Community website. 34 | /// 35 | /// The inventory for the given user. 36 | /// The Steam identifier. 37 | /// The SteamWeb instance for this Bot 38 | public static dynamic GetInventory(SteamID steamid, SteamWeb steamWeb) 39 | { 40 | string url = String.Format ( 41 | "http://steamcommunity.com/profiles/{0}/inventory/json/440/2/?trading=1", 42 | steamid.ConvertToUInt64 () 43 | ); 44 | 45 | try 46 | { 47 | string response = steamWeb.Fetch (url, "GET"); 48 | return JsonConvert.DeserializeObject (response); 49 | } 50 | catch (Exception) 51 | { 52 | return JsonConvert.DeserializeObject ("{\"success\":\"false\"}"); 53 | } 54 | } 55 | 56 | public uint NumSlots { get; set; } 57 | public Item[] Items { get; set; } 58 | public bool IsPrivate { get; private set; } 59 | public bool IsGood { get; private set; } 60 | 61 | protected Inventory (InventoryResult apiInventory) 62 | { 63 | NumSlots = apiInventory.num_backpack_slots; 64 | Items = apiInventory.items; 65 | IsPrivate = (apiInventory.status == "15"); 66 | IsGood = (apiInventory.status == "1"); 67 | } 68 | 69 | /// 70 | /// Check to see if user is Free to play 71 | /// 72 | /// Yes or no 73 | public bool IsFreeToPlay() 74 | { 75 | return this.NumSlots % 100 == 50; 76 | } 77 | 78 | public Item GetItem (ulong id) 79 | { 80 | // Check for Private Inventory 81 | if( this.IsPrivate ) 82 | throw new Exceptions.TradeException("Unable to access Inventory: Inventory is Private!"); 83 | 84 | return (Items == null ? null : Items.FirstOrDefault(item => item.Id == id)); 85 | } 86 | 87 | public List GetItemsByDefindex (int defindex) 88 | { 89 | // Check for Private Inventory 90 | if( this.IsPrivate ) 91 | throw new Exceptions.TradeException("Unable to access Inventory: Inventory is Private!"); 92 | 93 | return Items.Where(item => item.Defindex == defindex).ToList(); 94 | } 95 | 96 | public class Item 97 | { 98 | public int AppId = 440; 99 | public long ContextId = 2; 100 | 101 | [JsonProperty("id")] 102 | public ulong Id { get; set; } 103 | 104 | [JsonProperty("original_id")] 105 | public ulong OriginalId { get; set; } 106 | 107 | [JsonProperty("defindex")] 108 | public ushort Defindex { get; set; } 109 | 110 | [JsonProperty("level")] 111 | public byte Level { get; set; } 112 | 113 | [JsonProperty("quality")] 114 | public int Quality { get; set; } 115 | 116 | [JsonProperty("quantity")] 117 | public int RemainingUses { get; set; } 118 | 119 | [JsonProperty("origin")] 120 | public int Origin { get; set; } 121 | 122 | [JsonProperty("custom_name")] 123 | public string CustomName { get; set; } 124 | 125 | [JsonProperty("custom_desc")] 126 | public string CustomDescription { get; set; } 127 | 128 | [JsonProperty("flag_cannot_craft")] 129 | public bool IsNotCraftable { get; set; } 130 | 131 | [JsonProperty("flag_cannot_trade")] 132 | public bool IsNotTradeable { get; set; } 133 | 134 | [JsonProperty("attributes")] 135 | public ItemAttribute[] Attributes { get; set; } 136 | 137 | [JsonProperty("contained_item")] 138 | public Item ContainedItem { get; set; } 139 | } 140 | 141 | public class ItemAttribute 142 | { 143 | [JsonProperty("defindex")] 144 | public ushort Defindex { get; set; } 145 | 146 | [JsonProperty("value")] 147 | public string Value { get; set; } 148 | 149 | [JsonProperty("float_value")] 150 | public float FloatValue { get; set; } 151 | 152 | [JsonProperty("account_info")] 153 | public AccountInfo AccountInfo { get; set; } 154 | } 155 | 156 | public class AccountInfo 157 | { 158 | [JsonProperty("steamid")] 159 | public ulong SteamID { get; set; } 160 | 161 | [JsonProperty("personaname")] 162 | public string PersonaName { get; set; } 163 | } 164 | 165 | protected class InventoryResult 166 | { 167 | public string status { get; set; } 168 | 169 | public uint num_backpack_slots { get; set; } 170 | 171 | public Item[] items { get; set; } 172 | } 173 | 174 | protected class InventoryResponse 175 | { 176 | public InventoryResult result; 177 | } 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /SteamTrade/Schema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | 9 | namespace SteamTrade 10 | { 11 | /// 12 | /// This class represents the TF2 Item schema as deserialized from its 13 | /// JSON representation. 14 | /// 15 | public class Schema 16 | { 17 | private const string SchemaMutexName = "steam_bot_cache_file_mutex"; 18 | private const string SchemaApiUrlBase = "https://api.steampowered.com/IEconItems_440/GetSchemaItems/v1/?key="; 19 | private const string SchemaApiItemOriginNamesUrlBase = "https://api.steampowered.com/IEconItems_440/GetSchemaOverview/v1/?key="; 20 | 21 | /// 22 | /// Full file name for schema cache file. This value is only used when calling . If the time modified of the local copy is later than that of the server, local copy is used without downloading. Default value is %TEMP%\tf_schema.cache. 23 | /// 24 | public static string CacheFileFullName = Path.GetTempPath() + "\\tf_schema.cache"; 25 | 26 | /// 27 | /// Fetches the Tf2 Item schema. 28 | /// 29 | /// The API key. 30 | /// A deserialized instance of the Item Schema. 31 | /// 32 | /// The schema will be cached for future use if it is updated. 33 | /// 34 | 35 | public static Schema FetchSchema(string apiKey, string schemaLang = null) 36 | { 37 | var url = SchemaApiUrlBase + apiKey; 38 | if (schemaLang != null) 39 | url += "&format=json&language=" + schemaLang; 40 | 41 | // just let one thread/proc do the initial check/possible update. 42 | bool wasCreated; 43 | var mre = new EventWaitHandle(false, 44 | EventResetMode.ManualReset, SchemaMutexName, out wasCreated); 45 | 46 | // the thread that create the wait handle will be the one to 47 | // write the cache file. The others will wait patiently. 48 | if (!wasCreated) 49 | { 50 | bool signaled = mre.WaitOne(10000); 51 | 52 | if (!signaled) 53 | { 54 | return null; 55 | } 56 | } 57 | 58 | bool keepUpdating = true; 59 | SchemaResult schemaResult = new SchemaResult(); 60 | string tmpUrl = url; 61 | 62 | do 63 | { 64 | if(schemaResult.result != null) 65 | tmpUrl = url + "&start=" + schemaResult.result.Next; 66 | 67 | string result = new SteamWeb().Fetch(tmpUrl, "GET"); 68 | 69 | if (schemaResult.result == null || schemaResult.result.Items == null) 70 | { 71 | schemaResult = JsonConvert.DeserializeObject(result); 72 | } 73 | else 74 | { 75 | SchemaResult tempResult = JsonConvert.DeserializeObject(result); 76 | var items = schemaResult.result.Items.Concat(tempResult.result.Items); 77 | schemaResult.result.Items = items.ToArray(); 78 | schemaResult.result.Next = tempResult.result.Next; 79 | } 80 | 81 | if (schemaResult.result.Next <= schemaResult.result.Items.Count()) 82 | keepUpdating = false; 83 | 84 | } while (keepUpdating); 85 | 86 | 87 | //Get origin names 88 | string itemOriginUrl = SchemaApiItemOriginNamesUrlBase + apiKey; 89 | 90 | if (schemaLang != null) 91 | itemOriginUrl += "&format=json&language=" + schemaLang; 92 | 93 | string resp = new SteamWeb().Fetch(itemOriginUrl, "GET"); 94 | 95 | var itemOriginResult = JsonConvert.DeserializeObject(resp); 96 | 97 | schemaResult.result.OriginNames = itemOriginResult.result.OriginNames; 98 | 99 | // were done here. let others read. 100 | mre.Set(); 101 | DateTime schemaLastModified = DateTime.Now; 102 | 103 | return schemaResult.result ?? null; 104 | 105 | } 106 | 107 | // Gets the schema from the web or from the cached file. 108 | private static string GetSchemaString(HttpWebResponse response, DateTime schemaLastModified) 109 | { 110 | string result; 111 | bool mustUpdateCache = !File.Exists(CacheFileFullName) || schemaLastModified > File.GetCreationTime(CacheFileFullName); 112 | 113 | if (mustUpdateCache) 114 | { 115 | using(var reader = new StreamReader(response.GetResponseStream())) 116 | { 117 | result = reader.ReadToEnd(); 118 | 119 | File.WriteAllText(CacheFileFullName, result); 120 | File.SetCreationTime(CacheFileFullName, schemaLastModified); 121 | } 122 | } 123 | else 124 | { 125 | // read previously cached file. 126 | using(TextReader reader = new StreamReader(CacheFileFullName)) 127 | { 128 | result = reader.ReadToEnd(); 129 | } 130 | } 131 | 132 | return result; 133 | } 134 | 135 | 136 | [JsonProperty("status")] 137 | public int Status { get; set; } 138 | 139 | [JsonProperty("items_game_url")] 140 | public string ItemsGameUrl { get; set; } 141 | 142 | [JsonProperty("items")] 143 | public Item[] Items { get; set; } 144 | 145 | [JsonProperty("originNames")] 146 | public ItemOrigin[] OriginNames { get; set; } 147 | 148 | [JsonProperty("next")] 149 | public int Next { get; set; } 150 | 151 | /// 152 | /// Find an SchemaItem by it's defindex. 153 | /// 154 | public Item GetItem (int defindex) 155 | { 156 | foreach (Item item in Items) 157 | { 158 | if (item.Defindex == defindex) 159 | return item; 160 | } 161 | return null; 162 | } 163 | 164 | /// 165 | /// Returns all Items of the given crafting material. 166 | /// 167 | /// Item's craft_material_type JSON property. 168 | /// 169 | public List GetItemsByCraftingMaterial(string material) 170 | { 171 | return Items.Where(item => item.CraftMaterialType == material).ToList(); 172 | } 173 | 174 | public List GetItems() 175 | { 176 | return Items.ToList(); 177 | } 178 | 179 | public class ItemOrigin 180 | { 181 | [JsonProperty("origin")] 182 | public int Origin { get; set; } 183 | 184 | [JsonProperty("name")] 185 | public string Name { get; set; } 186 | } 187 | 188 | public class Item 189 | { 190 | [JsonProperty("name")] 191 | public string Name { get; set; } 192 | 193 | [JsonProperty("defindex")] 194 | public ushort Defindex { get; set; } 195 | 196 | [JsonProperty("item_class")] 197 | public string ItemClass { get; set; } 198 | 199 | [JsonProperty("item_type_name")] 200 | public string ItemTypeName { get; set; } 201 | 202 | [JsonProperty("item_name")] 203 | public string ItemName { get; set; } 204 | 205 | [JsonProperty("proper_name")] 206 | public bool ProperName { get; set; } 207 | 208 | [JsonProperty("craft_material_type")] 209 | public string CraftMaterialType { get; set; } 210 | 211 | [JsonProperty("used_by_classes")] 212 | public string[] UsableByClasses { get; set; } 213 | 214 | [JsonProperty("item_slot")] 215 | public string ItemSlot { get; set; } 216 | 217 | [JsonProperty("craft_class")] 218 | public string CraftClass { get; set; } 219 | 220 | [JsonProperty("item_quality")] 221 | public int ItemQuality { get; set; } 222 | } 223 | 224 | protected class SchemaResult 225 | { 226 | public Schema result { get; set; } 227 | } 228 | } 229 | } 230 | 231 | -------------------------------------------------------------------------------- /SteamTrade/SteamTrade.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.0 7 | 2.0 8 | {6CEC0333-81EB-40EE-85D1-941363626FC7} 9 | Library 10 | SteamTrade 11 | SteamTrade 12 | ..\ 13 | true 14 | v4.5 15 | 16 | 17 | 18 | True 19 | full 20 | False 21 | bin\Debug 22 | DEBUG; 23 | prompt 24 | 4 25 | False 26 | false 27 | 5 28 | 29 | 30 | full 31 | True 32 | bin\Release 33 | prompt 34 | 4 35 | False 36 | true 37 | false 38 | 39 | 40 | 41 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 42 | True 43 | 44 | 45 | ..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll 46 | 47 | 48 | ..\packages\SteamAuth.2.0.0\lib\net45\SteamAuth.dll 49 | True 50 | 51 | 52 | ..\packages\SteamKit2.1.8.1\lib\net45\SteamKit2.dll 53 | True 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /SteamTrade/TradeOffer/OfferSession.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SteamKit2; 3 | using System; 4 | using System.Collections.Specialized; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Net; 8 | 9 | namespace SteamTrade.TradeOffer 10 | { 11 | public class OfferSession 12 | { 13 | private readonly TradeOfferWebAPI webApi; 14 | private readonly SteamWeb steamWeb; 15 | 16 | internal JsonSerializerSettings JsonSerializerSettings { get; set; } 17 | 18 | internal const string SendUrl = "https://steamcommunity.com/tradeoffer/new/send"; 19 | 20 | public OfferSession(TradeOfferWebAPI webApi, SteamWeb steamWeb) 21 | { 22 | this.webApi = webApi; 23 | this.steamWeb = steamWeb; 24 | 25 | JsonSerializerSettings = new JsonSerializerSettings(); 26 | JsonSerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None; 27 | JsonSerializerSettings.Formatting = Formatting.None; 28 | } 29 | 30 | public TradeOfferAcceptResponse Accept(string tradeOfferId) 31 | { 32 | var data = new NameValueCollection(); 33 | data.Add("sessionid", steamWeb.SessionId); 34 | data.Add("serverid", "1"); 35 | data.Add("tradeofferid", tradeOfferId); 36 | 37 | string url = string.Format("https://steamcommunity.com/tradeoffer/{0}/accept", tradeOfferId); 38 | string referer = string.Format("https://steamcommunity.com/tradeoffer/{0}/", tradeOfferId); 39 | 40 | string resp = steamWeb.Fetch(url, "POST", data, false, referer, true); 41 | 42 | if (!String.IsNullOrEmpty(resp)) 43 | { 44 | try 45 | { 46 | var res = JsonConvert.DeserializeObject(resp); 47 | //steam can return 'null' response 48 | if (res != null) { 49 | res.Accepted = string.IsNullOrEmpty(res.TradeError); 50 | return res; 51 | } 52 | } 53 | catch (JsonException) 54 | { 55 | return new TradeOfferAcceptResponse { TradeError = "Error parsing server response: " + resp }; 56 | } 57 | } 58 | //if it didn't work as expected, check the state, maybe it was accepted after all 59 | var state = webApi.GetOfferState(tradeOfferId); 60 | return new TradeOfferAcceptResponse { Accepted = state == TradeOfferState.TradeOfferStateAccepted }; 61 | } 62 | 63 | public bool Decline(string tradeOfferId) 64 | { 65 | var data = new NameValueCollection(); 66 | data.Add("sessionid", steamWeb.SessionId); 67 | data.Add("serverid", "1"); 68 | data.Add("tradeofferid", tradeOfferId); 69 | 70 | string url = string.Format("https://steamcommunity.com/tradeoffer/{0}/decline", tradeOfferId); 71 | //should be http://steamcommunity.com/{0}/{1}/tradeoffers - id/profile persona/id64 ideally 72 | string referer = string.Format("https://steamcommunity.com/tradeoffer/{0}/", tradeOfferId); 73 | 74 | var resp = steamWeb.Fetch(url, "POST", data, false, referer); 75 | 76 | if (!String.IsNullOrEmpty(resp)) 77 | { 78 | try 79 | { 80 | var json = JsonConvert.DeserializeObject(resp); 81 | if (json.TradeOfferId != null && json.TradeOfferId == tradeOfferId) 82 | { 83 | return true; 84 | } 85 | } 86 | catch (JsonException jsex) 87 | { 88 | Debug.WriteLine(jsex); 89 | } 90 | } 91 | else 92 | { 93 | var state = webApi.GetOfferState(tradeOfferId); 94 | if (state == TradeOfferState.TradeOfferStateDeclined) 95 | { 96 | return true; 97 | } 98 | } 99 | return false; 100 | } 101 | 102 | public bool Cancel(string tradeOfferId) 103 | { 104 | var data = new NameValueCollection(); 105 | data.Add("sessionid", steamWeb.SessionId); 106 | data.Add("tradeofferid", tradeOfferId); 107 | data.Add("serverid", "1"); 108 | string url = string.Format("https://steamcommunity.com/tradeoffer/{0}/cancel", tradeOfferId); 109 | //should be http://steamcommunity.com/{0}/{1}/tradeoffers/sent/ - id/profile persona/id64 ideally 110 | string referer = string.Format("https://steamcommunity.com/tradeoffer/{0}/", tradeOfferId); 111 | 112 | var resp = steamWeb.Fetch(url, "POST", data, false, referer); 113 | 114 | if (!String.IsNullOrEmpty(resp)) 115 | { 116 | try 117 | { 118 | var json = JsonConvert.DeserializeObject(resp); 119 | if (json.TradeOfferId != null && json.TradeOfferId == tradeOfferId) 120 | { 121 | return true; 122 | } 123 | } 124 | catch (JsonException jsex) 125 | { 126 | Debug.WriteLine(jsex); 127 | } 128 | } 129 | else 130 | { 131 | var state = webApi.GetOfferState(tradeOfferId); 132 | if (state == TradeOfferState.TradeOfferStateCanceled) 133 | { 134 | return true; 135 | } 136 | } 137 | return false; 138 | } 139 | 140 | /// 141 | /// Creates a new counter offer 142 | /// 143 | /// A message to include with the trade offer 144 | /// The SteamID of the partner we are trading with 145 | /// The list of items we and they are going to trade 146 | /// The trade offer Id that will be created if successful 147 | /// The trade offer Id of the offer being countered 148 | /// 149 | public bool CounterOffer(string message, SteamID otherSteamId, TradeOffer.TradeStatus status, out string newTradeOfferId, string tradeOfferId) 150 | { 151 | if (String.IsNullOrEmpty(tradeOfferId)) 152 | { 153 | throw new ArgumentNullException("tradeOfferId", "Trade Offer Id must be set for counter offers."); 154 | } 155 | 156 | var data = new NameValueCollection(); 157 | data.Add("sessionid", steamWeb.SessionId); 158 | data.Add("serverid", "1"); 159 | data.Add("partner", otherSteamId.ConvertToUInt64().ToString()); 160 | data.Add("tradeoffermessage", message); 161 | data.Add("json_tradeoffer", JsonConvert.SerializeObject(status, JsonSerializerSettings)); 162 | data.Add("tradeofferid_countered", tradeOfferId); 163 | data.Add("trade_offer_create_params", "{}"); 164 | 165 | string referer = string.Format("https://steamcommunity.com/tradeoffer/{0}/", tradeOfferId); 166 | 167 | if (!Request(SendUrl, data, referer, tradeOfferId, out newTradeOfferId)) 168 | { 169 | var state = webApi.GetOfferState(tradeOfferId); 170 | if (state == TradeOfferState.TradeOfferStateCountered) 171 | { 172 | return true; 173 | } 174 | return false; 175 | } 176 | return true; 177 | } 178 | 179 | /// 180 | /// Creates a new trade offer 181 | /// 182 | /// A message to include with the trade offer 183 | /// The SteamID of the partner we are trading with 184 | /// The list of items we and they are going to trade 185 | /// The trade offer Id that will be created if successful 186 | /// True if successfully returns a newTradeOfferId, else false 187 | public bool SendTradeOffer(string message, SteamID otherSteamId, TradeOffer.TradeStatus status, out string newTradeOfferId) 188 | { 189 | var data = new NameValueCollection(); 190 | data.Add("sessionid", steamWeb.SessionId); 191 | data.Add("serverid", "1"); 192 | data.Add("partner", otherSteamId.ConvertToUInt64().ToString()); 193 | data.Add("tradeoffermessage", message); 194 | data.Add("json_tradeoffer", JsonConvert.SerializeObject(status, JsonSerializerSettings)); 195 | data.Add("trade_offer_create_params", "{}"); 196 | 197 | string referer = string.Format("https://steamcommunity.com/tradeoffer/new/?partner={0}", 198 | otherSteamId.AccountID); 199 | 200 | return Request(SendUrl, data, referer, null, out newTradeOfferId); 201 | } 202 | 203 | /// 204 | /// Creates a new trade offer with a token 205 | /// 206 | /// A message to include with the trade offer 207 | /// The SteamID of the partner we are trading with 208 | /// The list of items we and they are going to trade 209 | /// The token of the partner we are trading with 210 | /// The trade offer Id that will be created if successful 211 | /// True if successfully returns a newTradeOfferId, else false 212 | public bool SendTradeOfferWithToken(string message, SteamID otherSteamId, TradeOffer.TradeStatus status, 213 | string token, out string newTradeOfferId) 214 | { 215 | if (String.IsNullOrEmpty(token)) 216 | { 217 | throw new ArgumentNullException("token", "Partner trade offer token is missing"); 218 | } 219 | var offerToken = new OfferAccessToken() { TradeOfferAccessToken = token }; 220 | 221 | var data = new NameValueCollection(); 222 | data.Add("sessionid", steamWeb.SessionId); 223 | data.Add("serverid", "1"); 224 | data.Add("partner", otherSteamId.ConvertToUInt64().ToString()); 225 | data.Add("tradeoffermessage", message); 226 | data.Add("json_tradeoffer", JsonConvert.SerializeObject(status, JsonSerializerSettings)); 227 | data.Add("trade_offer_create_params", JsonConvert.SerializeObject(offerToken, JsonSerializerSettings)); 228 | 229 | string referer = string.Format("https://steamcommunity.com/tradeoffer/new/?partner={0}&token={1}", 230 | otherSteamId.AccountID, token); 231 | return Request(SendUrl, data, referer, null, out newTradeOfferId); 232 | } 233 | 234 | internal bool Request(string url, NameValueCollection data, string referer, string tradeOfferId, out string newTradeOfferId) 235 | { 236 | newTradeOfferId = ""; 237 | 238 | string resp = steamWeb.Fetch(url, "POST", data, false, referer); 239 | if (!String.IsNullOrEmpty(resp)) 240 | { 241 | try 242 | { 243 | var offerResponse = JsonConvert.DeserializeObject(resp); 244 | if (!String.IsNullOrEmpty(offerResponse.TradeOfferId)) 245 | { 246 | newTradeOfferId = offerResponse.TradeOfferId; 247 | return true; 248 | } 249 | else 250 | { 251 | //todo: log possible error 252 | Debug.WriteLine(offerResponse.TradeError); 253 | } 254 | } 255 | catch (JsonException jsex) 256 | { 257 | Debug.WriteLine(jsex); 258 | } 259 | } 260 | return false; 261 | } 262 | } 263 | 264 | public class NewTradeOfferResponse 265 | { 266 | [JsonProperty("tradeofferid")] 267 | public string TradeOfferId { get; set; } 268 | 269 | [JsonProperty("strError")] 270 | public string TradeError { get; set; } 271 | } 272 | 273 | public class OfferAccessToken 274 | { 275 | [JsonProperty("trade_offer_access_token")] 276 | public string TradeOfferAccessToken { get; set; } 277 | } 278 | 279 | public class TradeOfferAcceptResponse 280 | { 281 | public bool Accepted { get; set; } 282 | 283 | [JsonProperty("tradeid")] 284 | public string TradeId { get; set; } 285 | 286 | [JsonProperty("strError")] 287 | public string TradeError { get; set; } 288 | 289 | public TradeOfferAcceptResponse() 290 | { 291 | TradeId = String.Empty; 292 | TradeError = String.Empty; 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /SteamTrade/TradeOffer/TradeOfferManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using SteamKit2; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace SteamTrade.TradeOffer 9 | { 10 | public class TradeOfferManager 11 | { 12 | private readonly Dictionary knownTradeOffers = new Dictionary(); 13 | private readonly OfferSession session; 14 | private readonly TradeOfferWebAPI webApi; 15 | private readonly Queue unhandledTradeOfferUpdates; 16 | 17 | public DateTime LastTimeCheckedOffers { get; private set; } 18 | 19 | public TradeOfferManager(string apiKey, SteamWeb steamWeb) 20 | { 21 | if (apiKey == null) 22 | throw new ArgumentNullException("apiKey"); 23 | 24 | LastTimeCheckedOffers = DateTime.MinValue; 25 | webApi = new TradeOfferWebAPI(apiKey, steamWeb); 26 | session = new OfferSession(webApi, steamWeb); 27 | unhandledTradeOfferUpdates = new Queue(); 28 | } 29 | 30 | public delegate void TradeOfferUpdatedHandler(TradeOffer offer); 31 | 32 | /// 33 | /// Occurs when a new trade offer has been made by the other user 34 | /// 35 | public event TradeOfferUpdatedHandler OnTradeOfferUpdated; 36 | 37 | public void EnqueueUpdatedOffers() 38 | { 39 | DateTime startTime = DateTime.Now; 40 | 41 | var offersResponse = (LastTimeCheckedOffers == DateTime.MinValue 42 | ? webApi.GetAllTradeOffers() 43 | : webApi.GetAllTradeOffers(GetUnixTimeStamp(LastTimeCheckedOffers).ToString())); 44 | AddTradeOffersToQueue(offersResponse); 45 | 46 | LastTimeCheckedOffers = startTime - TimeSpan.FromMinutes(5); //Lazy way to make sure we don't miss any trade offers due to slightly differing clocks 47 | } 48 | 49 | private void AddTradeOffersToQueue(OffersResponse offers) 50 | { 51 | if (offers == null || offers.AllOffers == null) 52 | return; 53 | 54 | lock(unhandledTradeOfferUpdates) 55 | { 56 | foreach(var offer in offers.AllOffers) 57 | { 58 | unhandledTradeOfferUpdates.Enqueue(offer); 59 | } 60 | } 61 | } 62 | 63 | public bool HandleNextPendingTradeOfferUpdate() 64 | { 65 | Offer nextOffer; 66 | lock (unhandledTradeOfferUpdates) 67 | { 68 | if (!unhandledTradeOfferUpdates.Any()) 69 | { 70 | return false; 71 | } 72 | nextOffer = unhandledTradeOfferUpdates.Dequeue(); 73 | } 74 | 75 | return HandleTradeOfferUpdate(nextOffer); 76 | } 77 | 78 | private bool HandleTradeOfferUpdate(Offer offer) 79 | { 80 | if(knownTradeOffers.ContainsKey(offer.TradeOfferId) && knownTradeOffers[offer.TradeOfferId] == offer.TradeOfferState) 81 | { 82 | return false; 83 | } 84 | 85 | //make sure the api loaded correctly sometimes the items are missing 86 | if(IsOfferValid(offer)) 87 | { 88 | SendOfferToHandler(offer); 89 | } 90 | else 91 | { 92 | var resp = webApi.GetTradeOffer(offer.TradeOfferId); 93 | if(IsOfferValid(resp.Offer)) 94 | { 95 | SendOfferToHandler(resp.Offer); 96 | } 97 | else 98 | { 99 | Debug.WriteLine("Offer returned from steam api is not valid : " + resp.Offer.TradeOfferId); 100 | return false; 101 | } 102 | } 103 | return true; 104 | } 105 | 106 | private bool IsOfferValid(Offer offer) 107 | { 108 | bool hasItemsToGive = offer.ItemsToGive != null && offer.ItemsToGive.Count != 0; 109 | bool hasItemsToReceive = offer.ItemsToReceive != null && offer.ItemsToReceive.Count != 0; 110 | return hasItemsToGive || hasItemsToReceive; 111 | } 112 | 113 | private void SendOfferToHandler(Offer offer) 114 | { 115 | knownTradeOffers[offer.TradeOfferId] = offer.TradeOfferState; 116 | OnTradeOfferUpdated(new TradeOffer(session, offer)); 117 | } 118 | 119 | private uint GetUnixTimeStamp(DateTime dateTime) 120 | { 121 | var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 122 | return (uint)((dateTime.ToUniversalTime() - epoch).TotalSeconds); 123 | } 124 | 125 | public TradeOffer NewOffer(SteamID other) 126 | { 127 | return new TradeOffer(session, other); 128 | } 129 | 130 | public bool TryGetOffer(string offerId, out TradeOffer tradeOffer) 131 | { 132 | tradeOffer = null; 133 | var resp = webApi.GetTradeOffer(offerId); 134 | if (resp != null) 135 | { 136 | if (IsOfferValid(resp.Offer)) 137 | { 138 | tradeOffer = new TradeOffer(session, resp.Offer); 139 | return true; 140 | } 141 | else 142 | { 143 | Debug.WriteLine("Offer returned from steam api is not valid : " + resp.Offer.TradeOfferId); 144 | } 145 | } 146 | return false; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /SteamTrade/TradeOffer/TradeOfferWebAPI.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | 7 | namespace SteamTrade.TradeOffer 8 | { 9 | public class TradeOfferWebAPI 10 | { 11 | private readonly SteamWeb steamWeb; 12 | private readonly string apiKey; 13 | 14 | public TradeOfferWebAPI(string apiKey, SteamWeb steamWeb) 15 | { 16 | this.steamWeb = steamWeb; 17 | this.apiKey = apiKey; 18 | 19 | if (apiKey == null) 20 | { 21 | throw new ArgumentNullException("apiKey"); 22 | } 23 | } 24 | 25 | private const string BaseUrl = "http://api.steampowered.com/IEconService/{0}/{1}/{2}"; 26 | 27 | public OfferResponse GetTradeOffer(string tradeofferid) 28 | { 29 | string options = string.Format("?key={0}&tradeofferid={1}&language={2}&get_descriptions=1", apiKey, tradeofferid, "en_us"); 30 | string url = String.Format(BaseUrl, "GetTradeOffer", "v1", options); 31 | try 32 | { 33 | string response = steamWeb.Fetch(url, "GET", null, false); 34 | var result = JsonConvert.DeserializeObject>(response); 35 | return result.Response; 36 | } 37 | catch (Exception ex) 38 | { 39 | //todo log 40 | Debug.WriteLine(ex); 41 | } 42 | return new OfferResponse(); 43 | } 44 | 45 | public TradeOfferState GetOfferState(string tradeofferid) 46 | { 47 | var resp = GetTradeOffer(tradeofferid); 48 | if (resp != null && resp.Offer != null) 49 | { 50 | return resp.Offer.TradeOfferState; 51 | } 52 | return TradeOfferState.TradeOfferStateUnknown; 53 | } 54 | 55 | public OffersResponse GetAllTradeOffers(string timeHistoricalCutoff = "1389106496", string language = "en_us") 56 | { 57 | return GetTradeOffers(true, true, false, true, false, timeHistoricalCutoff, language); 58 | } 59 | 60 | public OffersResponse GetActiveTradeOffers(bool getSentOffers, bool getReceivedOffers, bool getDescriptions, string language = "en_us") 61 | { 62 | return GetTradeOffers(getSentOffers, getReceivedOffers, getDescriptions, true, false, "1389106496", language); 63 | } 64 | 65 | public OffersResponse GetTradeOffers(bool getSentOffers, bool getReceivedOffers, bool getDescriptions, bool activeOnly, bool historicalOnly, string timeHistoricalCutoff = "1389106496", string language = "en_us") 66 | { 67 | if (!getSentOffers && !getReceivedOffers) 68 | { 69 | throw new ArgumentException("getSentOffers and getReceivedOffers can't be both false"); 70 | } 71 | 72 | string options = string.Format("?key={0}&get_sent_offers={1}&get_received_offers={2}&get_descriptions={3}&language={4}&active_only={5}&historical_only={6}&time_historical_cutoff={7}", 73 | apiKey, BoolConverter(getSentOffers), BoolConverter(getReceivedOffers), BoolConverter(getDescriptions), language, BoolConverter(activeOnly), BoolConverter(historicalOnly), timeHistoricalCutoff); 74 | string url = String.Format(BaseUrl, "GetTradeOffers", "v1", options); 75 | string response = steamWeb.Fetch(url, "GET", null, false); 76 | try 77 | { 78 | var result = JsonConvert.DeserializeObject>(response); 79 | return result.Response; 80 | } 81 | catch (Exception ex) 82 | { 83 | //todo log 84 | Debug.WriteLine(ex); 85 | } 86 | return new OffersResponse(); 87 | } 88 | 89 | private static string BoolConverter(bool value) 90 | { 91 | return value ? "1" : "0"; 92 | } 93 | 94 | public TradeOffersSummary GetTradeOffersSummary(UInt32 timeLastVisit) 95 | { 96 | string options = string.Format("?key={0}&time_last_visit={1}", apiKey, timeLastVisit); 97 | string url = String.Format(BaseUrl, "GetTradeOffersSummary", "v1", options); 98 | 99 | try 100 | { 101 | string response = steamWeb.Fetch(url, "GET", null, false); 102 | var resp = JsonConvert.DeserializeObject>(response); 103 | 104 | return resp.Response; 105 | } 106 | catch (Exception ex) 107 | { 108 | //todo log 109 | Debug.WriteLine(ex); 110 | } 111 | return new TradeOffersSummary(); 112 | } 113 | 114 | private bool DeclineTradeOffer(ulong tradeofferid) 115 | { 116 | string options = string.Format("?key={0}&tradeofferid={1}", apiKey, tradeofferid); 117 | string url = String.Format(BaseUrl, "DeclineTradeOffer", "v1", options); 118 | Debug.WriteLine(url); 119 | string response = steamWeb.Fetch(url, "POST", null, false); 120 | dynamic json = JsonConvert.DeserializeObject(response); 121 | 122 | if (json == null || json.success != "1") 123 | { 124 | return false; 125 | } 126 | return true; 127 | } 128 | 129 | private bool CancelTradeOffer(ulong tradeofferid) 130 | { 131 | string options = string.Format("?key={0}&tradeofferid={1}", apiKey, tradeofferid); 132 | string url = String.Format(BaseUrl, "CancelTradeOffer", "v1", options); 133 | Debug.WriteLine(url); 134 | string response = steamWeb.Fetch(url, "POST", null, false); 135 | dynamic json = JsonConvert.DeserializeObject(response); 136 | 137 | if (json == null || json.success != "1") 138 | { 139 | return false; 140 | } 141 | return true; 142 | } 143 | } 144 | 145 | public class TradeOffersSummary 146 | { 147 | [JsonProperty("pending_received_count")] 148 | public int PendingReceivedCount { get; set; } 149 | 150 | [JsonProperty("new_received_count")] 151 | public int NewReceivedCount { get; set; } 152 | 153 | [JsonProperty("updated_received_count")] 154 | public int UpdatedReceivedCount { get; set; } 155 | 156 | [JsonProperty("historical_received_count")] 157 | public int HistoricalReceivedCount { get; set; } 158 | 159 | [JsonProperty("pending_sent_count")] 160 | public int PendingSentCount { get; set; } 161 | 162 | [JsonProperty("newly_accepted_sent_count")] 163 | public int NewlyAcceptedSentCount { get; set; } 164 | 165 | [JsonProperty("updated_sent_count")] 166 | public int UpdatedSentCount { get; set; } 167 | 168 | [JsonProperty("historical_sent_count")] 169 | public int HistoricalSentCount { get; set; } 170 | } 171 | 172 | public class ApiResponse 173 | { 174 | [JsonProperty("response")] 175 | public T Response { get; set; } 176 | } 177 | 178 | public class OfferResponse 179 | { 180 | [JsonProperty("offer")] 181 | public Offer Offer { get; set; } 182 | 183 | [JsonProperty("descriptions")] 184 | public List Descriptions { get; set; } 185 | } 186 | 187 | public class OffersResponse 188 | { 189 | [JsonProperty("trade_offers_sent")] 190 | public List TradeOffersSent { get; set; } 191 | 192 | [JsonProperty("trade_offers_received")] 193 | public List TradeOffersReceived { get; set; } 194 | 195 | [JsonProperty("descriptions")] 196 | public List Descriptions { get; set; } 197 | 198 | public IEnumerable AllOffers 199 | { 200 | get 201 | { 202 | if (TradeOffersSent == null) 203 | { 204 | return TradeOffersReceived; 205 | } 206 | 207 | return (TradeOffersReceived == null ? TradeOffersSent : TradeOffersSent.Union(TradeOffersReceived)); 208 | } 209 | } 210 | } 211 | 212 | public class Offer 213 | { 214 | [JsonProperty("tradeofferid")] 215 | public string TradeOfferId { get; set; } 216 | 217 | [JsonProperty("accountid_other")] 218 | public int AccountIdOther { get; set; } 219 | 220 | [JsonProperty("message")] 221 | public string Message { get; set; } 222 | 223 | [JsonProperty("expiration_time")] 224 | public int ExpirationTime { get; set; } 225 | 226 | [JsonProperty("trade_offer_state")] 227 | public TradeOfferState TradeOfferState { get; set; } 228 | 229 | [JsonProperty("items_to_give")] 230 | public List ItemsToGive { get; set; } 231 | 232 | [JsonProperty("items_to_receive")] 233 | public List ItemsToReceive { get; set; } 234 | 235 | [JsonProperty("is_our_offer")] 236 | public bool IsOurOffer { get; set; } 237 | 238 | [JsonProperty("time_created")] 239 | public int TimeCreated { get; set; } 240 | 241 | [JsonProperty("time_updated")] 242 | public int TimeUpdated { get; set; } 243 | 244 | [JsonProperty("from_real_time_trade")] 245 | public bool FromRealTimeTrade { get; set; } 246 | 247 | [JsonProperty("escrow_end_date")] 248 | public int EscrowEndDate { get; set; } 249 | 250 | [JsonProperty("confirmation_method")] 251 | private int confirmationMethod { get; set; } 252 | public TradeOfferConfirmationMethod ConfirmationMethod { get { return (TradeOfferConfirmationMethod)confirmationMethod; } set { confirmationMethod = (int)value; } } 253 | } 254 | 255 | public enum TradeOfferState 256 | { 257 | TradeOfferStateInvalid = 1, 258 | TradeOfferStateActive = 2, 259 | TradeOfferStateAccepted = 3, 260 | TradeOfferStateCountered = 4, 261 | TradeOfferStateExpired = 5, 262 | TradeOfferStateCanceled = 6, 263 | TradeOfferStateDeclined = 7, 264 | TradeOfferStateInvalidItems = 8, 265 | TradeOfferStateNeedsConfirmation = 9, 266 | TradeOfferStateCanceledBySecondFactor = 10, 267 | TradeOfferStateInEscrow = 11, 268 | TradeOfferStateUnknown 269 | } 270 | 271 | public enum TradeOfferConfirmationMethod 272 | { 273 | TradeOfferConfirmationMethodInvalid = 0, 274 | TradeOfferConfirmationMethodEmail = 1, 275 | TradeOfferConfirmationMethodMobileApp = 2 276 | } 277 | 278 | public class CEconAsset 279 | { 280 | [JsonProperty("appid")] 281 | public string AppId { get; set; } 282 | 283 | [JsonProperty("contextid")] 284 | public string ContextId { get; set; } 285 | 286 | [JsonProperty("assetid")] 287 | public string AssetId { get; set; } 288 | 289 | [JsonProperty("classid")] 290 | public string ClassId { get; set; } 291 | 292 | [JsonProperty("instanceid")] 293 | public string InstanceId { get; set; } 294 | 295 | [JsonProperty("amount")] 296 | public string Amount { get; set; } 297 | 298 | [JsonProperty("missing")] 299 | public bool IsMissing { get; set; } 300 | } 301 | 302 | public class AssetDescription 303 | { 304 | [JsonProperty("appid")] 305 | public int AppId { get; set; } 306 | 307 | [JsonProperty("classid")] 308 | public string ClassId { get; set; } 309 | 310 | [JsonProperty("instanceid")] 311 | public string InstanceId { get; set; } 312 | 313 | [JsonProperty("currency")] 314 | public bool IsCurrency { get; set; } 315 | 316 | [JsonProperty("background_color")] 317 | public string BackgroundColor { get; set; } 318 | 319 | [JsonProperty("icon_url")] 320 | public string IconUrl { get; set; } 321 | 322 | [JsonProperty("icon_url_large")] 323 | public string IconUrlLarge { get; set; } 324 | 325 | [JsonProperty("descriptions")] 326 | public List Descriptions { get; set; } 327 | 328 | [JsonProperty("tradable")] 329 | public bool IsTradable { get; set; } 330 | 331 | [JsonProperty("owner_actions")] 332 | public List OwnerActions { get; set; } 333 | 334 | [JsonProperty("name")] 335 | public string Name { get; set; } 336 | 337 | [JsonProperty("name_color")] 338 | public string NameColor { get; set; } 339 | 340 | [JsonProperty("type")] 341 | public string Type { get; set; } 342 | 343 | [JsonProperty("market_name")] 344 | public string MarketName { get; set; } 345 | 346 | [JsonProperty("market_hash_name")] 347 | public string MarketHashName { get; set; } 348 | } 349 | 350 | public class Description 351 | { 352 | [JsonProperty("type")] 353 | public string Type { get; set; } 354 | 355 | [JsonProperty("value")] 356 | public string Value { get; set; } 357 | } 358 | 359 | public class OwnerAction 360 | { 361 | [JsonProperty("link")] 362 | public string Link { get; set; } 363 | 364 | [JsonProperty("name")] 365 | public string Name { get; set; } 366 | } 367 | } -------------------------------------------------------------------------------- /SteamTrade/TradeWebAPI/TradeSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.Net; 4 | using Newtonsoft.Json; 5 | using SteamKit2; 6 | 7 | namespace SteamTrade.TradeWebAPI 8 | { 9 | /// 10 | /// This class provides the interface into the Web API for trading on the 11 | /// Steam network. 12 | /// 13 | public class TradeSession 14 | { 15 | private const string SteamTradeUrl = "http://steamcommunity.com/trade/{0}/"; 16 | 17 | private string sessionIdEsc; 18 | private string baseTradeURL; 19 | 20 | private readonly SteamWeb SteamWeb; 21 | private readonly SteamID OtherSID; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The Steam id of the other trading partner. 27 | /// The SteamWeb instance for this bot 28 | public TradeSession(SteamID otherSid, SteamWeb steamWeb) 29 | { 30 | OtherSID = otherSid; 31 | SteamWeb = steamWeb; 32 | 33 | Init(); 34 | } 35 | 36 | #region Trade status properties 37 | 38 | /// 39 | /// Gets the LogPos number of the current trade. 40 | /// 41 | /// This is not automatically updated by this class. 42 | internal int LogPos { get; set; } 43 | 44 | /// 45 | /// Gets the version number of the current trade. This increments on 46 | /// every item added or removed from a trade. 47 | /// 48 | /// This is not automatically updated by this class. 49 | internal int Version { get; set; } 50 | 51 | #endregion Trade status properties 52 | 53 | #region Trade Web API command methods 54 | 55 | /// 56 | /// Gets the trade status. 57 | /// 58 | /// A deserialized JSON object into 59 | /// 60 | /// This is the main polling method for trading and must be done at a 61 | /// periodic rate (probably around 1 second). 62 | /// 63 | internal TradeStatus GetStatus() 64 | { 65 | var data = new NameValueCollection (); 66 | 67 | data.Add ("sessionid", sessionIdEsc); 68 | data.Add ("logpos", "" + LogPos); 69 | data.Add ("version", "" + Version); 70 | 71 | string response = Fetch (baseTradeURL + "tradestatus", "POST", data); 72 | 73 | return JsonConvert.DeserializeObject (response); 74 | } 75 | 76 | 77 | /// 78 | /// Gets the foreign inventory. 79 | /// 80 | /// The other id. 81 | /// A dynamic JSON object. 82 | internal dynamic GetForeignInventory(SteamID otherId) 83 | { 84 | return GetForeignInventory(otherId, 440, 2); 85 | } 86 | internal dynamic GetForeignInventory(SteamID otherId, long contextId, int appid) 87 | { 88 | var data = new NameValueCollection(); 89 | 90 | data.Add ("sessionid", sessionIdEsc); 91 | data.Add ("steamid", "" + otherId.ConvertToUInt64()); 92 | data.Add ("appid", "" + appid); 93 | data.Add ("contextid", "" + contextId); 94 | 95 | try 96 | { 97 | string response = Fetch(baseTradeURL + "foreigninventory", "GET", data); 98 | return JsonConvert.DeserializeObject(response); 99 | } 100 | catch (Exception) 101 | { 102 | return JsonConvert.DeserializeObject("{\"success\":\"false\"}"); 103 | } 104 | } 105 | 106 | /// 107 | /// Sends a message to the user over the trade chat. 108 | /// 109 | internal bool SendMessageWebCmd(string msg) 110 | { 111 | var data = new NameValueCollection (); 112 | data.Add ("sessionid", sessionIdEsc); 113 | data.Add ("message", msg); 114 | data.Add ("logpos", "" + LogPos); 115 | data.Add ("version", "" + Version); 116 | 117 | string result = Fetch (baseTradeURL + "chat", "POST", data); 118 | 119 | dynamic json = JsonConvert.DeserializeObject(result); 120 | return IsSuccess(json); 121 | } 122 | 123 | /// 124 | /// Adds a specified item by its itemid. Since each itemid is 125 | /// unique to each item, you'd first have to find the item, or 126 | /// use AddItemByDefindex instead. 127 | /// 128 | /// 129 | /// Returns false if the item doesn't exist in the Bot's inventory, 130 | /// and returns true if it appears the item was added. 131 | /// 132 | internal bool AddItemWebCmd(ulong itemid, int slot,int appid,long contextid) 133 | { 134 | var data = new NameValueCollection (); 135 | 136 | data.Add ("sessionid", sessionIdEsc); 137 | data.Add ("appid", "" + appid); 138 | data.Add ("contextid", "" + contextid); 139 | data.Add ("itemid", "" + itemid); 140 | data.Add ("slot", "" + slot); 141 | 142 | string result = Fetch(baseTradeURL + "additem", "POST", data); 143 | 144 | dynamic json = JsonConvert.DeserializeObject(result); 145 | return IsSuccess(json); 146 | } 147 | 148 | /// 149 | /// Removes an item by its itemid. Read AddItem about itemids. 150 | /// Returns false if the item isn't in the offered items, or 151 | /// true if it appears it succeeded. 152 | /// 153 | internal bool RemoveItemWebCmd(ulong itemid, int slot, int appid, long contextid) 154 | { 155 | var data = new NameValueCollection (); 156 | 157 | data.Add ("sessionid", sessionIdEsc); 158 | data.Add ("appid", "" + appid); 159 | data.Add ("contextid", "" + contextid); 160 | data.Add ("itemid", "" + itemid); 161 | data.Add ("slot", "" + slot); 162 | 163 | string result = Fetch (baseTradeURL + "removeitem", "POST", data); 164 | 165 | dynamic json = JsonConvert.DeserializeObject(result); 166 | return IsSuccess(json); 167 | } 168 | 169 | /// 170 | /// Sets the bot to a ready status. 171 | /// 172 | internal bool SetReadyWebCmd(bool ready) 173 | { 174 | var data = new NameValueCollection (); 175 | data.Add ("sessionid", sessionIdEsc); 176 | data.Add ("ready", ready ? "true" : "false"); 177 | data.Add ("version", "" + Version); 178 | 179 | string result = Fetch (baseTradeURL + "toggleready", "POST", data); 180 | 181 | dynamic json = JsonConvert.DeserializeObject(result); 182 | return IsSuccess(json); 183 | } 184 | 185 | /// 186 | /// Accepts the trade from the user. Returns a deserialized 187 | /// JSON object. 188 | /// 189 | internal bool AcceptTradeWebCmd() 190 | { 191 | var data = new NameValueCollection (); 192 | 193 | data.Add ("sessionid", sessionIdEsc); 194 | data.Add ("version", "" + Version); 195 | 196 | string response = Fetch (baseTradeURL + "confirm", "POST", data); 197 | 198 | dynamic json = JsonConvert.DeserializeObject(response); 199 | return IsSuccess(json); 200 | } 201 | 202 | /// 203 | /// Cancel the trade. This calls the OnClose handler, as well. 204 | /// 205 | internal bool CancelTradeWebCmd () 206 | { 207 | var data = new NameValueCollection (); 208 | 209 | data.Add ("sessionid", sessionIdEsc); 210 | 211 | string result = Fetch (baseTradeURL + "cancel", "POST", data); 212 | 213 | dynamic json = JsonConvert.DeserializeObject(result); 214 | return IsSuccess(json); 215 | } 216 | 217 | private bool IsSuccess(dynamic json) 218 | { 219 | if(json == null) 220 | return false; 221 | try 222 | { 223 | //Sometimes, the response looks like this: {"success":false,"results":{"success":11}} 224 | //I believe this is Steam's way of asking the trade window (which is actually a webpage) to refresh, following a large successful update 225 | return (json.success == "true" || (json.results != null && json.results.success == "11")); 226 | } 227 | catch(Exception) 228 | { 229 | return false; 230 | } 231 | } 232 | 233 | #endregion Trade Web API command methods 234 | 235 | string Fetch (string url, string method, NameValueCollection data = null) 236 | { 237 | return SteamWeb.Fetch (url, method, data); 238 | } 239 | 240 | private void Init() 241 | { 242 | sessionIdEsc = Uri.UnescapeDataString(SteamWeb.SessionId); 243 | 244 | Version = 1; 245 | 246 | baseTradeURL = String.Format (SteamTradeUrl, OtherSID.ConvertToUInt64()); 247 | } 248 | } 249 | } 250 | 251 | -------------------------------------------------------------------------------- /SteamTrade/TradeWebAPI/TradeStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace SteamTrade.TradeWebAPI 6 | { 7 | public class TradeStatus 8 | { 9 | public string error { get; set; } 10 | 11 | public bool newversion { get; set; } 12 | 13 | public bool success { get; set; } 14 | 15 | public string tradeid { get; set; } 16 | 17 | public long trade_status { get; set; } 18 | 19 | public int version { get; set; } 20 | 21 | public int logpos { get; set; } 22 | 23 | public TradeUserObj me { get; set; } 24 | 25 | public TradeUserObj them { get; set; } 26 | 27 | public TradeEvent[] events { get; set; } 28 | 29 | public TradeEvent GetLastEvent() 30 | { 31 | if (events == null || events.Length == 0) 32 | return null; 33 | 34 | return events[events.Length - 1]; 35 | } 36 | 37 | public TradeEvent[] GetAllEvents() 38 | { 39 | if (events == null) 40 | return new TradeEvent[0]; 41 | 42 | return events; 43 | } 44 | } 45 | 46 | public class TradeEvent : IEquatable 47 | { 48 | public string steamid { get; set; } 49 | 50 | public int action { get; set; } 51 | 52 | public ulong timestamp { get; set; } 53 | 54 | public int appid { get; set; } 55 | 56 | public string text { get; set; } 57 | 58 | public long contextid { get; set; } 59 | 60 | public ulong assetid { get; set; } 61 | 62 | /// 63 | /// Determins if the TradeEvent is equal to another. 64 | /// 65 | /// TradeEvent to compare to 66 | /// True if equal, false if not 67 | public bool Equals(TradeEvent other) 68 | { 69 | return this.steamid == other.steamid && this.action == other.action 70 | && this.timestamp == other.timestamp && this.appid == other.appid 71 | && this.text == other.text && this.contextid == other.contextid 72 | && this.assetid == other.assetid; 73 | } 74 | } 75 | 76 | public class TradeUserObj 77 | { 78 | public int ready { get; set; } 79 | 80 | public int confirmed { get; set; } 81 | 82 | public int sec_since_touch { get; set; } 83 | 84 | public bool connection_pending { get; set; } 85 | 86 | public dynamic assets { get; set; } 87 | 88 | /// 89 | /// Gets the array from the TradeUserObj. Use this instead 90 | /// of the property directly. 91 | /// 92 | /// An array of 93 | public TradeUserAssets[] GetAssets() 94 | { 95 | var tradeUserAssetses = new List(); 96 | 97 | // if items were added in trade the type is an array like so: 98 | // a normal JSON array 99 | // "assets": [ 100 | // { 101 | // "assetid": "1693638354", 102 | // } 103 | //], 104 | if (assets.GetType() == typeof(JArray)) 105 | { 106 | foreach (var asset in assets) 107 | { 108 | tradeUserAssetses.Add(new TradeUserAssets((int)asset.appid, (long)asset.contextid, (ulong)asset.assetid, (int)asset.amount)); 109 | } 110 | } 111 | else if (assets.GetType() == typeof(JObject)) 112 | { 113 | // when items are removed from trade they look like this: 114 | // a JSON object like a "list" 115 | // (item in trade slot 1 was removed) 116 | // "assets": { 117 | // "2": { 118 | // "assetid": "1745718856", 119 | // }, 120 | // "3": { 121 | // "assetid": "1690644335", 122 | // } 123 | //}, 124 | foreach (JProperty obj in assets) 125 | { 126 | dynamic value = obj.Value; 127 | tradeUserAssetses.Add(new TradeUserAssets((int)value.appid, (long)value.contextid, (ulong)value.assetid, (int)value.amount)); 128 | } 129 | } 130 | 131 | return tradeUserAssetses.ToArray(); 132 | } 133 | } 134 | 135 | public class TradeUserAssets : IEquatable, IComparable 136 | { 137 | /// Iventory type 138 | public long contextid { get; private set; } 139 | /// itemid 140 | public ulong assetid { get; private set; } 141 | public int appid { get; private set; } 142 | public int amount { get; private set; } 143 | 144 | public TradeUserAssets(int appid, long contextid, ulong assetid, int amount = 1) 145 | { 146 | this.appid = appid; 147 | this.contextid = contextid; 148 | this.assetid = assetid; 149 | this.amount = amount; 150 | } 151 | 152 | public bool Equals(TradeUserAssets other) 153 | { 154 | return (CompareTo(other) == 0); 155 | } 156 | 157 | public override bool Equals(object other) 158 | { 159 | TradeUserAssets otherCasted = other as TradeUserAssets; 160 | return (otherCasted != null && Equals(otherCasted)); 161 | } 162 | 163 | public override int GetHashCode() 164 | { 165 | return contextid.GetHashCode() ^ assetid.GetHashCode() ^ appid.GetHashCode() ^ amount.GetHashCode(); 166 | } 167 | 168 | public int CompareTo(TradeUserAssets other) 169 | { 170 | if(appid != other.appid) 171 | return (appid < other.appid ? -1 : 1); 172 | if(contextid != other.contextid) 173 | return (contextid < other.contextid ? -1 : 1); 174 | if(assetid != other.assetid) 175 | return (assetid < other.assetid ? -1 : 1); 176 | if(amount != other.amount) 177 | return (amount < other.amount ? -1 : 1); 178 | return 0; 179 | } 180 | 181 | public override string ToString() 182 | { 183 | return string.Format("id:{0}, appid:{1}, contextid:{2}, amount:{3}",assetid,appid,contextid,amount); 184 | } 185 | } 186 | 187 | public enum TradeEventType 188 | { 189 | ItemAdded = 0, //itemid = "assetid" 190 | ItemRemoved = 1, //itemid = "assetid" 191 | UserSetReady = 2, 192 | UserSetUnReady = 3, 193 | UserAccept = 4, 194 | //5 = ?? Maybe some sort of cancel? 195 | ModifiedCurrency = 6, 196 | UserChat = 7 //message = "text" 197 | } 198 | } -------------------------------------------------------------------------------- /SteamTrade/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /SteamTrade/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /triggerappveyor.txt: -------------------------------------------------------------------------------- 1 | yo appveyor, build me now. 2 | Trigger tests! --------------------------------------------------------------------------------