├── .gitattributes
├── .gitignore
├── EasyBuyout.sln
├── EasyBuyout
├── App.config
├── App.xaml
├── App.xaml.cs
├── CurrencyAddModToRare.ico
├── EasyBuyout.csproj
├── Hooks
│ ├── ClipboardHandler.cs
│ ├── KeyEmulator.cs
│ ├── MouseHook.cs
│ └── WindowDiscovery.cs
├── Item
│ ├── Item.cs
│ └── Key.cs
├── League
│ ├── Deserializers.cs
│ └── LeagueManager.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Prices
│ ├── Deserializers.cs
│ ├── PriceManager.cs
│ ├── PriceboxWindow.xaml
│ └── PriceboxWindow.xaml.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Settings
│ ├── Config.cs
│ ├── ManualLeagueWindow.xaml
│ ├── ManualLeagueWindow.xaml.cs
│ ├── SettingsWindow.xaml
│ ├── SettingsWindow.xaml.cs
│ └── Source
│ │ ├── SourceApi.cs
│ │ └── SourceSite.cs
└── Updater
│ ├── Deserializers.cs
│ ├── UpdateWindow.xaml
│ └── UpdateWindow.xaml.cs
├── LICENCE.md
├── README.md
└── screenshots
└── action.gif
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/EasyBuyout.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2020
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyBuyout", "EasyBuyout\EasyBuyout.csproj", "{9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {F0DBC2CC-8470-41FC-B251-752BAA2CAAC2}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/EasyBuyout/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/EasyBuyout/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/EasyBuyout/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Threading;
9 |
10 | namespace EasyBuyout {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/EasyBuyout/CurrencyAddModToRare.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibbydev/EasyBuyout/4a1874f97f22f52e57c195f4f8ea0a0bf8abc429/EasyBuyout/CurrencyAddModToRare.ico
--------------------------------------------------------------------------------
/EasyBuyout/EasyBuyout.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9E2BE7DD-720D-45D8-AB74-C98AAA525BB4}
8 | WinExe
9 | EasyBuyout
10 | EasyBuyout
11 | v4.6.1
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 | CurrencyAddModToRare.ico
38 |
39 |
40 | EasyBuyout.App
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 4.0
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | MSBuild:Compile
64 | Designer
65 |
66 |
67 |
68 |
69 |
70 |
71 | ManualLeagueWindow.xaml
72 |
73 |
74 |
75 |
76 |
77 | SettingsWindow.xaml
78 |
79 |
80 |
81 |
82 |
83 | UpdateWindow.xaml
84 |
85 |
86 | Designer
87 | MSBuild:Compile
88 |
89 |
90 | MSBuild:Compile
91 | Designer
92 |
93 |
94 | App.xaml
95 | Code
96 |
97 |
98 |
99 |
100 |
101 | MainWindow.xaml
102 | Code
103 |
104 |
105 | Designer
106 | MSBuild:Compile
107 |
108 |
109 | Designer
110 | MSBuild:Compile
111 |
112 |
113 | Designer
114 | MSBuild:Compile
115 |
116 |
117 |
118 |
119 |
120 | PriceboxWindow.xaml
121 |
122 |
123 | Code
124 |
125 |
126 | True
127 | True
128 | Resources.resx
129 |
130 |
131 | True
132 | Settings.settings
133 | True
134 |
135 |
136 | ResXFileCodeGenerator
137 | Resources.Designer.cs
138 |
139 |
140 | SettingsSingleFileGenerator
141 | Settings.Designer.cs
142 |
143 |
144 |
145 |
146 | Designer
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/EasyBuyout/Hooks/ClipboardHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Windows.Forms;
4 |
5 | namespace EasyBuyout {
6 | // from: http://stackoverflow.com/questions/2226920/how-to-monitor-clipboard-content-changes-in-c
7 | /// Provides notifications when the contents of the clipboard is updated.
8 | public sealed class ClipboardNotification {
9 | /// Occurs when the contents of the clipboard is updated.
10 | public static event EventHandler ClipboardUpdate;
11 | private static NotificationForm _form = new NotificationForm();
12 |
13 | /// Hidden form to recieve the WM_CLIPBOARDUPDATE message.
14 | private class NotificationForm : Form {
15 | public NotificationForm() {
16 | NativeMethods.SetParent(Handle, NativeMethods.HWND_MESSAGE);
17 | NativeMethods.AddClipboardFormatListener(Handle);
18 | }
19 |
20 | protected override void WndProc(ref Message m) {
21 | if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
22 | ClipboardUpdate?.Invoke(null, new EventArgs());
23 |
24 | base.WndProc(ref m);
25 | }
26 | }
27 | }
28 |
29 | internal static class NativeMethods {
30 | // See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx
31 | public const int WM_CLIPBOARDUPDATE = 0x031D;
32 | public static IntPtr HWND_MESSAGE = new IntPtr(-3);
33 |
34 | // See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
35 | [DllImport("user32.dll", SetLastError = true)]
36 | [return: MarshalAs(UnmanagedType.Bool)]
37 | public static extern bool AddClipboardFormatListener(IntPtr hwnd);
38 |
39 | // See http://msdn.microsoft.com/en-us/library/ms633541%28v=vs.85%29.aspx
40 | // See http://msdn.microsoft.com/en-us/library/ms649033%28VS.85%29.aspx
41 | [DllImport("user32.dll", SetLastError = true)]
42 | public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/EasyBuyout/Hooks/KeyEmulator.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Threading;
3 |
4 | namespace EasyBuyout.hooks {
5 | public sealed class KeyEmulator {
6 | public static void SendCtrlC() {
7 | keybd_event(0x11, 0, 0, 0); // 0x11 - ctrl, 0 - down
8 | keybd_event(0x43, 0, 0, 0); // 0x43 - c, 0 - down
9 | Thread.Sleep(1);
10 | keybd_event(0x43, 0, 2, 0); // 0x43 - c, 2 - up
11 | keybd_event(0x11, 0, 2, 0); // 0x11 - ctrl, 2 - up
12 | }
13 |
14 | public static void SendLMB() {
15 | mouse_event(0x0002, 0, 0, 0, 0); // 0x0002 - LMB_down
16 | Thread.Sleep(1);
17 | mouse_event(0x0004, 0, 0, 0, 0); // 0x0002 - LMB_up
18 | }
19 |
20 | [DllImport("user32.dll")]
21 | private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
22 |
23 | [DllImport("user32.dll")]
24 | private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, uint dwExtraInf);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/EasyBuyout/Hooks/MouseHook.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace EasyBuyout {
6 | public static class MouseHook {
7 | private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
8 | public static event EventHandler MouseAction;
9 | private static LowLevelMouseProc _proc = HookCallback;
10 | private static IntPtr _hookID = IntPtr.Zero;
11 | private const int WH_MOUSE_LL = 14;
12 |
13 | public static void Start() { if (_hookID == IntPtr.Zero) _hookID = SetHook(_proc); }
14 | public static void Stop() { UnhookWindowsHookEx(_hookID); }
15 |
16 | private static IntPtr SetHook(LowLevelMouseProc proc) {
17 | using (Process curProcess = Process.GetCurrentProcess())
18 | using (ProcessModule curModule = curProcess.MainModule) {
19 | return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
20 | }
21 | }
22 |
23 | private static IntPtr HookCallback(
24 | int nCode, IntPtr wParam, IntPtr lParam) {
25 | if (nCode >= 0 && MouseMessages.WM_RBUTTONDOWN == (MouseMessages)wParam) {
26 | MouseAction?.Invoke(null, new EventArgs());
27 | }
28 | return CallNextHookEx(_hookID, nCode, wParam, lParam);
29 | }
30 |
31 | private enum MouseMessages {
32 | WM_LBUTTONDOWN = 0x0201,
33 | WM_LBUTTONUP = 0x0202,
34 | WM_MOUSEMOVE = 0x0200,
35 | WM_MOUSEWHEEL = 0x020A,
36 | WM_RBUTTONDOWN = 0x0204,
37 | WM_RBUTTONUP = 0x0205
38 | }
39 |
40 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
41 | private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
42 |
43 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
44 | [return: MarshalAs(UnmanagedType.Bool)]
45 | private static extern bool UnhookWindowsHookEx(IntPtr hhk);
46 |
47 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
48 | private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
49 |
50 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
51 | private static extern IntPtr GetModuleHandle(string lpModuleName);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/EasyBuyout/Hooks/WindowDiscovery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Text;
4 |
5 | namespace EasyBuyout.hooks {
6 | public sealed class WindowDiscovery {
7 | private const int NChars = 256;
8 |
9 | public static string GetActiveWindowTitle() {
10 | var buff = new StringBuilder(NChars);
11 | var handle = GetForegroundWindow();
12 |
13 | return GetWindowText(handle, buff, NChars) > 0 ? buff.ToString() : null;
14 | }
15 |
16 | [DllImport("user32.dll")]
17 | private static extern IntPtr GetForegroundWindow();
18 |
19 | [DllImport("user32.dll")]
20 | private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/EasyBuyout/Item/Item.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace EasyBuyout.Item {
7 | public class Item {
8 | public readonly List Errors = new List();
9 | public volatile bool Discard;
10 | public readonly Key Key = new Key();
11 |
12 | ///
13 | /// Default constructor
14 | ///
15 | /// Copied item data string
16 | public Item(string rawInput) {
17 | var splitRaw = SplitRawInput(rawInput);
18 | // If any errors occured in split method
19 | if (Discard) return;
20 |
21 | // Process item data
22 | ParseItemData(splitRaw);
23 | }
24 |
25 | //-----------------------------------------------------------------------------------------------------------
26 | // Main parsing methods
27 | //-----------------------------------------------------------------------------------------------------------
28 |
29 | ///
30 | /// Converts input into an array, replaces newlines with delimiters, splits groups
31 | ///
32 | /// Formatted list of item data groups
33 | private string[] SplitRawInput(string rawInput) {
34 | // All Ctrl+C'd item data must contain "--------" or "Rarity:"
35 | if (!rawInput.Contains("--------") || !rawInput.Contains("Rarity: ")) {
36 | Errors.Add("Clipboard contents did not match Ctrl+C'd item data pattern");
37 | Discard = true;
38 | return null;
39 | }
40 |
41 | // Expects input in the form of whatever copied itemdata looks like:
42 | // Rarity: Normal
43 | // Majestic Plate
44 | // --------
45 | // Armour: 530
46 | // --------
47 | // Requirements:
48 | // Level: 53
49 | // Str: 144
50 | // --------
51 | // Sockets: B-R
52 | // --------
53 | // Item Level: 100
54 |
55 | // Convert it to a string like this:
56 | // "Rarity: Normal|Majestic Plate|::|Armour: 530|::|Requirements:|Level: 53|Str: 144|::|Sockets: B-R |::|Item Level: 100"
57 |
58 | var formattedRaw = rawInput.Replace("--------", "::");
59 | formattedRaw = formattedRaw.Replace("\r\n", "|");
60 | formattedRaw = formattedRaw.Remove(formattedRaw.Length - 1);
61 |
62 | // Then get something like this:
63 | // {
64 | // "Rarity: Normal|Majestic Plate",
65 | // "Armour: 530",
66 | // "Requirements:|Level: 53|Str: 144",
67 | // "Sockets: B-R ",
68 | // "Item Level: 100"
69 | // }
70 |
71 | var splitRaw = formattedRaw.Split(new[] {"|::|"}, StringSplitOptions.None);
72 |
73 | // Check validity of item data
74 | foreach (var line in splitRaw) {
75 | if (line.StartsWith("Unidentified")) {
76 | Errors.Add("Item is unidentified");
77 | Discard = true;
78 | break;
79 | }
80 |
81 | if (line.StartsWith("Note: ")) {
82 | Errors.Add("Cannot add buyout notes to items that already have them");
83 | Discard = true;
84 | break;
85 | }
86 | }
87 |
88 | return splitRaw;
89 | }
90 |
91 | ///
92 | /// Checks what data is present and then calls specific parse methods
93 | ///
94 | public void ParseItemData(string[] splitRaw) {
95 | // Find name, type and rarity
96 | var nameLine = splitRaw[0].Split('|');
97 | var rarity = TrimProperty(nameLine[0]);
98 |
99 | Key.Name = nameLine[1];
100 | Key.TypeLine = nameLine.Length > 2 ? nameLine[2] : null;
101 |
102 | if (rarity.Equals("Rare")) {
103 | Key.Name = Key.TypeLine;
104 | Key.TypeLine = null;
105 | }
106 |
107 | // Find item's frame type
108 | Key.FrameType = Parse_FrameType(splitRaw, rarity);
109 | // If any errors occured in parse function
110 | if (Discard) return;
111 |
112 | switch (Key.FrameType) {
113 | case 3:
114 | // Find largest link group, if present
115 | Key.Links = ParseSockets(splitRaw);
116 | // Find unique item variant, if present
117 | Key.Variation = ParseVariant(splitRaw, Key.Name);
118 | break;
119 | case 4:
120 | ParseGemData(splitRaw, Key.Name);
121 | break;
122 | }
123 | }
124 |
125 | ///
126 | /// Extract frame type from item data
127 | ///
128 | ///
129 | ///
130 | ///
131 | private int Parse_FrameType(string[] splitRaw, string rarity) {
132 | if (rarity == null) {
133 | // Shouldn't run
134 | Discard = true;
135 | Errors.Add("Invalid rarity");
136 | }
137 |
138 | switch (rarity) {
139 | case "Unique":
140 | return 3;
141 | case "Gem":
142 | return 4;
143 | case "Currency":
144 | return 5;
145 | case "Divination Card":
146 | return 6;
147 | }
148 |
149 | // Loop through lines checking for specific strings
150 | foreach (var line in splitRaw) {
151 | if (line.Contains("Right-click to add this prophecy")) {
152 | // Prophecy
153 | return 8;
154 | }
155 |
156 | if (line.Contains("Travel to this Map by using it in")) {
157 | // Standardized maps
158 | return 0;
159 | }
160 |
161 | if (line.Equals("Relic Unique")) {
162 | // Relics
163 | return 9;
164 | }
165 | }
166 |
167 | // If other methods failed use frame type 0
168 | switch (rarity) {
169 | case "Normal":
170 | case "Magic":
171 | case "Rare":
172 | return 0;
173 | }
174 |
175 | // Could not determine the frame type
176 | Discard = true;
177 | Errors.Add("Unknown frame type");
178 | return -1;
179 | }
180 |
181 | //-----------------------------------------------------------------------------------------------------------
182 | // Specific parsing methods
183 | //-----------------------------------------------------------------------------------------------------------
184 |
185 | ///
186 | /// Parses socket data if present and finds largest link
187 | ///
188 | ///
189 | /// Socket count (5 or 6) or null
190 | private static int? ParseSockets(string[] splitRaw) {
191 | // Find what line index socket data is under
192 | var lineIndex = FindIndexOf("Sockets:", splitRaw);
193 |
194 | // Item doesn't have socket data
195 | if (lineIndex == -1) return null;
196 |
197 | // Get socket line: "Sockets: B G-R-R-G-R " -> "B G-R-R-G-R" (note the extra whitespace prefix)
198 | var socketData = TrimProperty(splitRaw[lineIndex]);
199 |
200 | // Create array for counting link groups
201 | int[] links = {1, 1, 1, 1, 1};
202 | var counter = 0;
203 |
204 | // Count links
205 | for (var i = 1; i < socketData.Length; i += 2) {
206 | if (socketData[i] == '-')
207 | links[counter]++;
208 | else
209 | counter++;
210 | }
211 |
212 | // Find largest link
213 | var largestLink = links.Concat(new[] {0}).Max();
214 |
215 | // Return largest link or null
216 | return largestLink > 4 ? (int?) largestLink : null;
217 | }
218 |
219 | ///
220 | /// As some specific items have multiple variants, distinguish them
221 | ///
222 | /// Variant string or null if none
223 | private static string ParseVariant(string[] splitRaw, string name) {
224 | // Find what line index "Item Level:" is under
225 | var exModIndex = FindIndexOf("Item Level:", splitRaw);
226 |
227 | // Couldn't find index of item level
228 | if (exModIndex == -1) return null;
229 |
230 | // Explicit mods are located under "Item Level:" line
231 | var explicitMods = splitRaw[exModIndex + 1].Split('|');
232 |
233 | // Check variation
234 | switch (name) {
235 | case "Atziri's Splendour":
236 | // Take the first explicit mod and replace variables with constants
237 | var genericMod = string.Join("#", Regex.Split(explicitMods[0], @"\d+"));
238 |
239 | // Compare the first generic explicit mod to the preset definitions
240 | switch (genericMod) {
241 | case "#% increased Armour, Evasion and Energy Shield":
242 | return "ar/ev/es";
243 | case "#% increased Armour and Energy Shield":
244 | return explicitMods[1].Contains("Life") ? "ar/es/li" : "ar/es";
245 | case "#% increased Evasion and Energy Shield":
246 | return explicitMods[1].Contains("Life") ? "ev/es/li" : "ev/es";
247 | case "#% increased Armour and Evasion":
248 | return "ar/ev";
249 | case "#% increased Armour":
250 | return "ar";
251 | case "#% increased Evasion Rating":
252 | return "ev";
253 | case "+# to maximum Energy Shield":
254 | return "es";
255 | }
256 |
257 | break;
258 |
259 | case "Vessel of Vinktar":
260 | foreach (var mod in explicitMods) {
261 | if (mod.Contains("to Spells")) return "spells";
262 | if (mod.Contains("to Attacks")) return "attacks";
263 | if (mod.Contains("Penetrates")) return "penetration";
264 | if (mod.Contains("Converted to")) return "conversion";
265 | }
266 |
267 | break;
268 |
269 | case "Doryani's Invitation":
270 | // Explicit mods for Doryani's Invitation are located 2 lines under the "Item Level:" line
271 | explicitMods = splitRaw[exModIndex + 2].Split('|');
272 |
273 | foreach (var mod in explicitMods) {
274 | if (mod.Contains("Lightning Damage")) return "lightning";
275 | if (mod.Contains("Physical Damage")) return "physical";
276 | if (mod.Contains("Fire Damage")) return "fire";
277 | if (mod.Contains("Cold Damage")) return "cold";
278 | }
279 |
280 | break;
281 |
282 | case "Yriel's Fostering":
283 | foreach (var mod in explicitMods) {
284 | if (mod.Contains("Chaos Damage")) return "snake";
285 | if (mod.Contains("Physical Damage")) return "ursa";
286 | if (mod.Contains("Attack and Movement")) return "rhoa";
287 | }
288 |
289 | break;
290 |
291 | case "Volkuur's Guidance":
292 | // Figure out on what line explicit mods are located (due to enchantments)
293 | for (int i = exModIndex; i < splitRaw.Length; i++) {
294 | if (splitRaw[i].Contains("to maximum Life")) {
295 | explicitMods = splitRaw[i].Split('|');
296 | break;
297 | }
298 | }
299 |
300 | // Figure out item variant
301 | foreach (var mod in explicitMods) {
302 | if (mod.Contains("Lightning Damage")) return "lightning";
303 | if (mod.Contains("Fire Damage")) return "fire";
304 | if (mod.Contains("Cold Damage")) return "cold";
305 | }
306 |
307 | break;
308 |
309 | case "Impresence":
310 | // Explicit mods for Impresence are located 2 lines under the "Item Level:" line
311 | explicitMods = splitRaw[exModIndex + 2].Split('|');
312 |
313 | foreach (var mod in explicitMods) {
314 | if (mod.Contains("Lightning Damage")) return "lightning";
315 | if (mod.Contains("Physical Damage")) return "physical";
316 | if (mod.Contains("Chaos Damage")) return "chaos";
317 | if (mod.Contains("Fire Damage")) return "fire";
318 | if (mod.Contains("Cold Damage")) return "cold";
319 | }
320 |
321 | break;
322 |
323 | case "Lightpoacher":
324 | case "Shroud of the Lightless":
325 | case "Bubonic Trail":
326 | case "Tombfist":
327 | case "Command of the Pit":
328 | case "Hale Negator":
329 | // Figure out on what line explicit mods are located (due to enchantments)
330 | for (int i = exModIndex; i < splitRaw.Length; i++) {
331 | if (splitRaw[i].Contains("Abyssal Socket")) {
332 | explicitMods = splitRaw[i].Split('|');
333 | break;
334 | }
335 | }
336 |
337 | // Check how many abyssal sockets the item has
338 | switch (explicitMods[0]) {
339 | case "Has 2 Abyssal Sockets":
340 | return "2 sockets";
341 | case "Has 1 Abyssal Socket":
342 | return "1 socket";
343 | }
344 |
345 | break;
346 |
347 | case "The Beachhead":
348 | // Find the line that contains map tier info (e.g "Map Tier: 15")
349 | foreach (var line in splitRaw) {
350 | var subLine = line.Split('|');
351 |
352 | foreach (var prop in subLine) {
353 | if (prop.StartsWith("Map Tier: ")) {
354 | // Return tier info (e.g "15")
355 | return prop.Substring(prop.IndexOf(' ', prop.IndexOf(' ') + 1) + 1);
356 | }
357 | }
358 | }
359 |
360 | break;
361 |
362 | case "Combat Focus":
363 | // Explicit mods for Combat Focus are located 2 lines under the "Item Level:" line
364 | explicitMods = splitRaw[exModIndex + 2].Split('|');
365 |
366 | foreach (var mod in explicitMods) {
367 | if (mod.Contains("choose Lightning")) return "lightning";
368 | if (mod.Contains("choose Cold")) return "cold";
369 | if (mod.Contains("choose Fire")) return "fire";
370 | }
371 |
372 | break;
373 | }
374 |
375 | // Nothing matched, eh?
376 | return null;
377 | }
378 |
379 | ///
380 | /// Extracts gem information from item data
381 | ///
382 | private void ParseGemData(string[] splitRaw, string name) {
383 | var pattern = new Regex(@"\d+");
384 | int? level = null;
385 | int quality = 0;
386 |
387 | // Second line will contain gem data
388 | var gemLines = splitRaw[1].Split('|');
389 |
390 | // Find gem level and quality
391 | foreach (var line in gemLines) {
392 | if (line.Contains("Level:")) {
393 | level = int.Parse(pattern.Match(line).Value);
394 | } else if (line.Contains("Quality:")) {
395 | quality = int.Parse(pattern.Match(line).Value);
396 | }
397 | }
398 |
399 | if (level == null) {
400 | Discard = true;
401 | Errors.Add("Couldn't extract gem data");
402 | return;
403 | }
404 |
405 | // Find gem corrupted status
406 | var corrupted = splitRaw[splitRaw.Length - 1].Contains("Corrupted");
407 |
408 | // Special gems have special needs
409 | if (name.Contains("Empower") || name.Contains("Enlighten") || name.Contains("Enhance")) {
410 | quality = quality < 10 ? 0 : 20;
411 | // Quality doesn't matter for lvl 3 and 4
412 | if (level > 2) quality = 0;
413 | } else {
414 | // Logically speaking, a lvl 17 gem is closer to lvl1 in terms of value compared to lvl20
415 | if (level < 18) {
416 | level = 1;
417 | } else if (level < 21) {
418 | level = 20;
419 | }
420 |
421 | if (quality < 17) {
422 | quality = 0;
423 | } else if (quality < 22) {
424 | quality = 20;
425 | } else {
426 | quality = 23;
427 | }
428 |
429 | // Gets rid of specific gems (lvl:1 quality:23-> lvl:1 quality:20)
430 | if (level < 20 && quality > 20) {
431 | quality = 20;
432 | }
433 |
434 | if (level > 20 || quality > 20) {
435 | corrupted = true;
436 | } else if (name.Contains("Vaal")) {
437 | corrupted = true;
438 | }
439 | }
440 |
441 | Key.GemLevel = level;
442 | Key.GemQuality = quality;
443 | Key.GemCorrupted = corrupted;
444 | }
445 |
446 | //-----------------------------------------------------------------------------------------------------------
447 | // Generic parsing methods
448 | //-----------------------------------------------------------------------------------------------------------
449 |
450 | ///
451 | /// Finds the index of the element the element in the haystack that starts with the needle
452 | ///
453 | /// String to search for
454 | /// Array to search it in
455 | ///
456 | private static int FindIndexOf(string needle, string[] haystack) {
457 | for (int i = 0; i < haystack.Length; i++) {
458 | if (haystack[i].StartsWith(needle)) {
459 | return i;
460 | }
461 | }
462 |
463 | return -1;
464 | }
465 |
466 | ///
467 | /// Takes input as "Item Level: 55" or "Sockets: B - B B " and returns "55" or "B - B B"
468 | ///
469 | /// "Sockets: B - B B "
470 | /// "B - B B"
471 | private static string TrimProperty(string str) {
472 | var index = str.IndexOf(' ') + 1;
473 | return str.Substring(index, str.Length - index).Trim();
474 | }
475 | }
476 | }
--------------------------------------------------------------------------------
/EasyBuyout/Item/Key.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using EasyBuyout.Prices;
4 |
5 | namespace EasyBuyout.Item {
6 | public class Key {
7 | public string Name, TypeLine, Variation;
8 | public int FrameType;
9 | public int? Links, MapTier, GemLevel, GemQuality;
10 | public bool? GemCorrupted;
11 |
12 | public Key() { }
13 |
14 | ///
15 | /// Poe.ninja's API is a bit retarded so everything gets fixed here
16 | ///
17 | ///
18 | ///
19 | public Key(string category, PoeNinjaEntry entry) {
20 | // Currency is handled in a different API format
21 | if (category.Equals("Currency")) {
22 | Name = entry.currencyTypeName;
23 | FrameType = 5;
24 | return;
25 | }
26 |
27 | // Fragments are handled in a different API format
28 | if (category.Equals("Fragment")) {
29 | Name = entry.currencyTypeName;
30 | FrameType = 0;
31 | return;
32 | }
33 |
34 | Name = entry.name;
35 | TypeLine = entry.baseType;
36 | FrameType = entry.itemClass;
37 |
38 | Links = entry.links;
39 | MapTier = entry.mapTier;
40 | GemLevel = entry.gemLevel;
41 | GemQuality = entry.gemQuality;
42 | GemCorrupted = entry.corrupted;
43 | Variation = entry.variant;
44 |
45 | // For gems
46 | if (FrameType == 4) {
47 | // Gems have variants?
48 | Variation = null;
49 | }
50 |
51 | // For non-maps
52 | if (MapTier == 0) {
53 | MapTier = null;
54 | } else if (FrameType != 3) {
55 | // Maps have random frametypes
56 | FrameType = 0;
57 | // Can't determine map series from item data so map variation is unusable for us
58 | Variation = null;
59 | }
60 |
61 | // For items with variations
62 | if (Variation != null) {
63 | Variation = FixPoeNinjaVariant(Name, Variation);
64 | }
65 |
66 |
67 | // For non- 6L and 5L
68 | if (Links == 0) {
69 | Links = null;
70 | }
71 |
72 | // For non-gems
73 | if (FrameType != 4) {
74 | GemLevel = null;
75 | GemQuality = null;
76 | GemCorrupted = null;
77 | }
78 |
79 | // For normal maps
80 | if (MapTier == 0 && FrameType == 0) {
81 | // Name is type line for some reason
82 | TypeLine = null;
83 | // Variant refers to map type
84 | Variation = null;
85 | }
86 | }
87 |
88 | private string FixPoeNinjaVariant(string name, string variant) {
89 | if (variant == null) {
90 | return null;
91 | }
92 |
93 | switch (name) {
94 | case "Atziri's Splendour":
95 | switch (variant) {
96 | case "Armour/ES": return "ar/es";
97 | case "ES": return "es";
98 | case "Armour/Evasion": return "ar/ev";
99 | case "Armour/ES/Life": return "ar/es/li";
100 | case "Evasion/ES": return "ev/es";
101 | case "Armour/Evasion/ES": return "ar/ev/es";
102 | case "Evasion": return "ev";
103 | case "Evasion/ES/Life": return "ev/es/li";
104 | case "Armour": return "ar";
105 | }
106 |
107 | break;
108 |
109 | case "Yriel's Fostering":
110 | switch (variant) {
111 | case "Bleeding": return "ursa";
112 | case "Poison": return "snake";
113 | case "Maim": return "rhoa";
114 | }
115 |
116 | break;
117 |
118 | case "Volkuur's Guidance":
119 | switch (variant) {
120 | case "Lightning": return "lightning";
121 | case "Fire": return "fire";
122 | case "Cold": return "cold";
123 | }
124 |
125 | break;
126 |
127 | case "Lightpoacher":
128 | case "Shroud of the Lightless":
129 | case "Bubonic Trail":
130 | case "Tombfist":
131 | case "Command of the Pit":
132 | case "Hale Negator":
133 | switch (variant) {
134 | case "2 Jewels": return "2 sockets";
135 | case "1 Jewel": return "1 socket";
136 | }
137 |
138 | break;
139 |
140 | case "Vessel of Vinktar":
141 | switch (variant) {
142 | case "Added Attacks": return "attacks";
143 | case "Added Spells": return "spells";
144 | case "Penetration": return "penetration";
145 | case "Conversion": return "conversion";
146 | }
147 |
148 | break;
149 |
150 | case "Doryani's Invitation":
151 | switch (variant) {
152 | case null: // Bug on poe.ninja's end
153 | case "Physical": return "physical";
154 | case "Fire": return "fire";
155 | case "Cold": return "cold";
156 | case "Lightning": return "lightning";
157 | }
158 |
159 | break;
160 |
161 | case "Impresence":
162 | switch (variant) {
163 | case "Chaos": return "chaos";
164 | case "Physical": return "physical";
165 | case "Fire": return "fire";
166 | case "Cold": return "cold";
167 | case "Lightning": return "lightning";
168 | }
169 |
170 | break;
171 |
172 | case "The Beachhead":
173 | return MapTier.ToString();
174 | }
175 |
176 | Console.WriteLine($"no var match for {this} var {variant}");
177 |
178 | return null;
179 | }
180 |
181 | public override bool Equals(object obj) {
182 | if (obj == null) {
183 | return false;
184 | }
185 |
186 | if (typeof(Key) != obj.GetType()) {
187 | return false;
188 | }
189 |
190 | var other = (Key) obj;
191 |
192 | if (FrameType != other.FrameType) {
193 | return false;
194 | }
195 |
196 | if (!Name?.Equals(other.Name) ?? (other.Name != null)) {
197 | return false;
198 | }
199 |
200 | if (!TypeLine?.Equals(other.TypeLine) ?? (other.TypeLine != null)) {
201 | return false;
202 | }
203 |
204 | if (!Variation?.Equals(other.Variation) ?? (other.Variation != null)) {
205 | return false;
206 | }
207 |
208 | if (!Links?.Equals(other.Links) ?? (other.Links != null)) {
209 | return false;
210 | }
211 |
212 | if (!GemLevel?.Equals(other.GemLevel) ?? (other.GemLevel != null)) {
213 | return false;
214 | }
215 |
216 | if (!GemQuality?.Equals(other.GemQuality) ?? (other.GemQuality != null)) {
217 | return false;
218 | }
219 |
220 | if (!MapTier?.Equals(other.MapTier) ?? (other.MapTier != null)) {
221 | return false;
222 | }
223 |
224 | if (!GemCorrupted?.Equals(other.GemCorrupted) ?? (other.GemCorrupted != null)) {
225 | return false;
226 | }
227 |
228 | return true;
229 | }
230 |
231 | public override int GetHashCode() {
232 | var hash = 3;
233 |
234 | hash = 53 * hash + (Name?.GetHashCode() ?? 0);
235 | hash = 53 * hash + (TypeLine?.GetHashCode() ?? 0);
236 | hash = 53 * hash + (Variation?.GetHashCode() ?? 0);
237 | hash = 53 * hash + (Links.GetHashCode());
238 | hash = 53 * hash + (GemLevel.GetHashCode());
239 | hash = 53 * hash + (GemQuality.GetHashCode());
240 | hash = 53 * hash + (MapTier.GetHashCode());
241 | hash = 53 * hash + (GemCorrupted.GetHashCode());
242 | hash = 53 * hash + FrameType;
243 |
244 | return hash;
245 | }
246 |
247 | public override string ToString() {
248 | var builder = new StringBuilder();
249 |
250 | builder.Append($"{Name}");
251 |
252 | if (TypeLine != null) {
253 | builder.Append($", {TypeLine}");
254 | }
255 |
256 | if (Variation != null) {
257 | builder.Append($" ({Variation})");
258 | }
259 |
260 | return builder.ToString();
261 | }
262 |
263 | public string ToFullString() {
264 | var builder = new StringBuilder();
265 |
266 | builder.Append($"name:'{Name}'");
267 |
268 | if (TypeLine != null) {
269 | builder.Append($",type:'{TypeLine}'");
270 | }
271 |
272 | builder.Append($",frame:'{FrameType}'");
273 |
274 | if (Variation != null) {
275 | builder.Append($",var:'{Variation}'");
276 | }
277 |
278 | if (Links != null) {
279 | builder.Append($",links:'{Links}'");
280 | }
281 |
282 | if (MapTier != null) {
283 | builder.Append($",tier:'{MapTier}'");
284 | }
285 |
286 | if (GemLevel != null) {
287 | builder.Append($",lvl:'{GemLevel}'");
288 | }
289 |
290 | if (GemQuality != null) {
291 | builder.Append($",quality:'{GemQuality}'");
292 | }
293 |
294 | if (GemCorrupted != null) {
295 | builder.Append($",corrupted:'{GemCorrupted}'");
296 | }
297 |
298 | return builder.ToString();
299 | }
300 | }
301 | }
--------------------------------------------------------------------------------
/EasyBuyout/League/Deserializers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EasyBuyout.League {
8 | ///
9 | /// Deserializer for https://www.pathofexile.com/api/trade/data/leagues
10 | ///
11 | public sealed class LeagueParcel {
12 | public List result { get; set; }
13 | }
14 |
15 | ///
16 | /// Deserializer for https://www.pathofexile.com/api/trade/data/leagues
17 | ///
18 | public sealed class LeagueEntry {
19 | public string text { get; set; }
20 | public string id { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/EasyBuyout/League/LeagueManager.cs:
--------------------------------------------------------------------------------
1 | using EasyBuyout.Settings;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net;
5 | using System.Web.Script.Serialization;
6 |
7 | namespace EasyBuyout.League {
8 | public class LeagueManager {
9 | private readonly Config _config;
10 | private readonly Action _logAction;
11 | private readonly WebClient _webClient;
12 |
13 | ///
14 | /// Constructor
15 | ///
16 | ///
17 | ///
18 | ///
19 | public LeagueManager(Config config, WebClient webClient, Action logAction) {
20 | _config = config;
21 | _webClient = webClient;
22 | _logAction = logAction;
23 | }
24 |
25 | ///
26 | /// Get list of active leagues
27 | ///
28 | public string[] GetLeagueList() {
29 | var leagueEntries = DownloadLeagueList();
30 |
31 | if (leagueEntries == null) {
32 | return null;
33 | }
34 |
35 | var leagues = new string[leagueEntries.Count];
36 |
37 | for (var i = 0; i < leagueEntries.Count; i++) {
38 | leagues[i] = leagueEntries[i].id;
39 | }
40 |
41 | return leagues;
42 | }
43 |
44 | ///
45 | /// Downloads active leagues from PoE's official API
46 | ///
47 | /// List of active leagues or null on failure
48 | private List DownloadLeagueList() {
49 | // WebClient can only handle one connection per instance. It is bad practice to have multiple WebClients.
50 | while (_webClient.IsBusy) {
51 | System.Threading.Thread.Sleep(10);
52 | }
53 |
54 | try {
55 | _webClient.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36");
56 | var jsonString = _webClient.DownloadString(_config.LeagueApiUrl);
57 | return new JavaScriptSerializer().Deserialize(jsonString).result;
58 | } catch (Exception ex) {
59 | _logAction(ex.ToString(), MainWindow.Flair.Error);
60 | return null;
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/EasyBuyout/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/EasyBuyout/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using EasyBuyout.hooks;
2 | using EasyBuyout.Prices;
3 | using EasyBuyout.Settings;
4 | using System;
5 | using System.Net;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using EasyBuyout.League;
11 | using EasyBuyout.Updater;
12 |
13 | namespace EasyBuyout {
14 | ///
15 | /// Interaction logic for MainWindow.xaml
16 | ///
17 | public partial class MainWindow {
18 | public enum Flair {
19 | Info,
20 | Warn,
21 | Error,
22 | Critical
23 | }
24 |
25 | private readonly SettingsWindow _settingsWindow;
26 | private readonly PriceboxWindow _priceBox;
27 | private readonly PriceManager _priceManager;
28 | private readonly Config _config;
29 | private static TextBox _console;
30 | private volatile bool _flagClipBoardPaste;
31 |
32 | private readonly LeagueManager _leagueManager;
33 | private readonly ManualLeagueWindow _manualLeagueWindow;
34 |
35 | ///
36 | /// Initializes the form and sets event listeners
37 | ///
38 | public MainWindow() {
39 | _config = new Config();
40 |
41 | // Web client setup
42 | var webClient = new WebClient {Encoding = System.Text.Encoding.UTF8};
43 | webClient.Headers.Add("user-agent", $"{_config.ProgramTitle} {_config.ProgramVersion}");
44 | ServicePointManager.SecurityProtocol =
45 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
46 |
47 | // Define event handlers
48 | ClipboardNotification.ClipboardUpdate += Event_clipboard;
49 | MouseHook.MouseAction += Event_mouse;
50 |
51 | // Initialize the UI components
52 | InitializeComponent();
53 |
54 | // Set objects that need to be accessed from outside
55 | _console = console_window;
56 |
57 | // Object setup
58 | _settingsWindow = new SettingsWindow(_config, Log);
59 | _priceManager = new PriceManager(_config, webClient, Log);
60 | _priceBox = new PriceboxWindow();
61 | var updateWindow = new UpdateWindow(_config, webClient, Log);
62 | _leagueManager = new LeagueManager(_config, webClient, Log);
63 |
64 | // Set window title
65 | Title = $"{_config.ProgramTitle} {_config.ProgramVersion}";
66 | Log($"{_config.ProgramTitle} {_config.ProgramVersion} by Siegrest");
67 |
68 | Task.Run(() => {
69 | // Check for updates
70 | updateWindow.Run();
71 | // Query PoE API for active league list and add them to settings selectors
72 | UpdateLeagues();
73 | });
74 | }
75 |
76 | //-----------------------------------------------------------------------------------------------------------
77 | // External event handlers
78 | //-----------------------------------------------------------------------------------------------------------
79 |
80 | ///
81 | /// Mouse event handler
82 | ///
83 | ///
84 | ///
85 | private void Event_mouse(object sender, EventArgs e) {
86 | // Do not run if user has not pressed run button
87 | if (!_config.FlagRun) {
88 | return;
89 | }
90 |
91 | // Send Ctrl+C on mouse click
92 | KeyEmulator.SendCtrlC();
93 | }
94 |
95 | ///
96 | /// Clipboard event handler
97 | ///
98 | private void Event_clipboard(object sender, EventArgs e) {
99 | // Do not run if user has not pressed run button
100 | if (!_config.FlagRun) {
101 | return;
102 | }
103 |
104 | // At this point there should be text in the clipboard
105 | if (!Clipboard.ContainsText()) {
106 | return;
107 | }
108 |
109 | // TODO: check limits
110 | // Sleep to allow clipboard write action to finish
111 | Thread.Sleep(_config.ClipboardWriteDelay);
112 |
113 | // Get clipboard contents
114 | var clipboardString = Clipboard.GetText();
115 |
116 | // This event handles *all* clipboard events
117 | if (clipboardString.StartsWith("~")) {
118 | Task.Run(() => ClipBoard_NotePasteTask());
119 | } else {
120 | Task.Run(() => ClipBoard_ItemParseTask(clipboardString));
121 | }
122 | }
123 |
124 | ///
125 | /// Takes the clipboard data, parses it and depending whether the item was desirable, outputs data
126 | ///
127 | /// Item data from the clipboard
128 | private void ClipBoard_ItemParseTask(string clipboardString) {
129 | var item = new Item.Item(clipboardString);
130 |
131 | // If the item was shite, discard it
132 | if (item.Discard) {
133 | System.Media.SystemSounds.Asterisk.Play();
134 |
135 | foreach (var error in item.Errors) {
136 | Log(error, Flair.Error);
137 | }
138 |
139 | return;
140 | }
141 |
142 | // Get entries associated with item keys
143 | var entry = _priceManager.GetEntry(item.Key);
144 |
145 | // Display error
146 | if (entry == null) {
147 | Log($"No match for: {item.Key}", Flair.Warn);
148 |
149 | if (_config.FlagShowOverlay) {
150 | DisplayPriceBox("No match");
151 | }
152 |
153 | return;
154 | }
155 |
156 | double price;
157 | if (_config.LowerPricePercentage > 0) {
158 | price = (entry.Value * (100 - _config.LowerPricePercentage)) / 100.0;
159 | } else {
160 | price = entry.Value;
161 | }
162 |
163 | // Round price
164 | var pow = Math.Pow(10, _config.PricePrecision);
165 | price = Math.Round(price * pow) / pow;
166 |
167 | // Replace "," with "." due to game limitations
168 | var strPrice = price.ToString().Replace(',', '.');
169 | var note = $"{_config.NotePrefix} {strPrice} chaos";
170 |
171 | // Log item price to main console
172 | Log($"{item.Key}: {price} chaos");
173 |
174 | if (_config.FlagShowOverlay) {
175 | DisplayPriceBox($"{price} chaos");
176 | return;
177 | }
178 |
179 | // Raise flag allowing next cb event to be processed
180 | if (_config.FlagSendNote) {
181 | _flagClipBoardPaste = true;
182 | Dispatcher.Invoke(() => Clipboard.SetText(note));
183 | }
184 | }
185 |
186 | ///
187 | /// Called via task, this method pastes the current clipboard contents and presses enter
188 | ///
189 | private void ClipBoard_NotePasteTask() {
190 | if (_flagClipBoardPaste) {
191 | _flagClipBoardPaste = false;
192 | } else {
193 | return;
194 | }
195 |
196 | Thread.Sleep(_config.PasteDelay);
197 |
198 | // Paste clipboard contents
199 | System.Windows.Forms.SendKeys.SendWait("^v");
200 |
201 | // Send enter key if checkbox is checked
202 | if (_config.FlagSendEnter) {
203 | System.Windows.Forms.SendKeys.SendWait("{ENTER}");
204 | }
205 |
206 | _flagClipBoardPaste = false;
207 | }
208 |
209 | //-----------------------------------------------------------------------------------------------------------
210 | // WPF Event handlers
211 | //-----------------------------------------------------------------------------------------------------------
212 |
213 | ///
214 | /// Unhooks hooks on program exit
215 | ///
216 | ///
217 | ///
218 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
219 | MouseHook.Stop();
220 | Application.Current.Shutdown();
221 | }
222 |
223 | ///
224 | /// GetLeagueList button event handler
225 | ///
226 | private void Button_Run_Click(object sender, RoutedEventArgs e) {
227 | if (Button_Run.Content.ToString() == "Run") {
228 | Button_Run.Content = "Pause";
229 | _config.FlagRun = true;
230 | Log("Service started");
231 | MouseHook.Start();
232 | } else {
233 | Button_Run.Content = "Run";
234 | _config.FlagRun = false;
235 | Log("Service paused");
236 | }
237 | }
238 |
239 | ///
240 | /// Calculates position and opens settings window
241 | ///
242 | private void Button_Settings_Click(object sender, RoutedEventArgs e) {
243 | _settingsWindow.Left = Left + Width / 2 - _settingsWindow.Width / 2;
244 | _settingsWindow.Top = Top + Height / 2 - _settingsWindow.Height / 2;
245 | _settingsWindow.ShowDialog();
246 | }
247 |
248 | ///
249 | /// Prevents main window from resizing whenever anything is written to main textbox
250 | ///
251 | ///
252 | ///
253 | private void Window_Loaded(object sender, RoutedEventArgs e) {
254 | // Reset the SizeToContent property
255 | SizeToContent = SizeToContent.Manual;
256 |
257 | // Position window to screen center manually
258 | try {
259 | var rect = SystemParameters.WorkArea;
260 | Left = (rect.Width - Width) / 2 + rect.Left;
261 | Top = (rect.Height - Height) / 2 + rect.Top;
262 | } catch (Exception ex) {
263 | Console.WriteLine(ex);
264 | }
265 | }
266 |
267 | //-----------------------------------------------------------------------------------------------------------
268 | // Other
269 | //-----------------------------------------------------------------------------------------------------------
270 |
271 | ///
272 | /// Set the content, position and visibility of the pricebox with one method
273 | ///
274 | /// String to be displayed in the overlay
275 | public void DisplayPriceBox(string content) {
276 | Application.Current.Dispatcher.Invoke(() => {
277 | _priceBox.Content = content;
278 | _priceBox.SetPosition();
279 | _priceBox.Show();
280 | });
281 | }
282 |
283 | ///
284 | /// Prints text to window in console-like fashion, prefixes a timestamp
285 | ///
286 | /// String to print
287 | /// Status code of message
288 | public void Log(string msg, Flair flair = Flair.Info) {
289 | var prefix = "";
290 |
291 | switch (flair) {
292 | case Flair.Info:
293 | prefix = "INFO";
294 | break;
295 | case Flair.Warn:
296 | prefix = "WARN";
297 | break;
298 | case Flair.Error:
299 | prefix = "ERROR";
300 | break;
301 | case Flair.Critical:
302 | prefix = "CRITICAL";
303 | break;
304 | }
305 |
306 | Application.Current.Dispatcher.Invoke(() => {
307 | _console.AppendText($"[{DateTime.Now:HH:mm:ss}][{prefix}] {msg}\n");
308 | _console.ScrollToEnd();
309 | });
310 | }
311 |
312 | ///
313 | /// Adds provided league names to league selector
314 | ///
315 | public void UpdateLeagues() {
316 | Log("Updating league list...");
317 |
318 | var leagues = _leagueManager.GetLeagueList();
319 | if (leagues == null) {
320 | Log("Unable to update leagues");
321 |
322 | ComboBox_League.Items.Add(_config.ManualLeagueDisplay);
323 | ComboBox_League.SelectedIndex = 0;
324 |
325 | return;
326 | }
327 |
328 | Application.Current.Dispatcher.Invoke(() => {
329 | foreach (var league in leagues) {
330 | ComboBox_League.Items.Add(league);
331 | }
332 |
333 | ComboBox_League.Items.Add(_config.ManualLeagueDisplay);
334 | ComboBox_League.SelectedIndex = 0;
335 | Button_Download.IsEnabled = true;
336 |
337 | Log("League list updated");
338 | });
339 | }
340 |
341 | ///
342 | /// Download price data on button press
343 | ///
344 | private void Button_Download_Click(object sender, RoutedEventArgs e) {
345 | _config.SelectedLeague = (string) ComboBox_League.SelectedValue;
346 |
347 | // User has chosen to set league manually
348 | if (_config.SelectedLeague == _config.ManualLeagueDisplay) {
349 | var manualLeagueWindow = new ManualLeagueWindow();
350 | manualLeagueWindow.ShowDialog();
351 |
352 | if (string.IsNullOrEmpty(manualLeagueWindow.input)) {
353 | Log("Invalid league", Flair.Error);
354 | return;
355 | }
356 |
357 | _config.SelectedLeague = manualLeagueWindow.input;
358 | }
359 |
360 | Button_Download.IsEnabled = false;
361 |
362 | Task.Run(() => {
363 | Log($"Downloading data for {_config.SelectedLeague}");
364 |
365 | _priceManager.Download();
366 |
367 | Application.Current.Dispatcher.Invoke(() => {
368 | Button_Download.IsEnabled = true;
369 | Button_Run.IsEnabled = true;
370 | });
371 |
372 | Log("Download finished");
373 | });
374 | }
375 | }
376 | }
--------------------------------------------------------------------------------
/EasyBuyout/Prices/Deserializers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EasyBuyout.Prices {
8 | ///
9 | /// Used to populate local database
10 | ///
11 | public sealed class Entry {
12 | public double Value { get; set; }
13 | public int Quantity { get; set; }
14 |
15 | public Entry(double value, int quantity) {
16 | Value = value;
17 | Quantity = quantity;
18 | }
19 |
20 | public Entry(Entry input) {
21 | Value = input.Value;
22 | Quantity = input.Quantity;
23 | }
24 | }
25 |
26 | ///
27 | /// Used to deserialize http://poe.ninja API calls
28 | ///
29 | public sealed class PoeNinjaEntry {
30 | public int? id { get; set; }
31 | public string name { get; set; }
32 | public int count { get; set; }
33 | public string baseType { get; set; }
34 | public int itemClass { get; set; }
35 | public string currencyTypeName { get; set; }
36 | public string variant { get; set; }
37 | public int? links { get; set; }
38 | public int mapTier { get; set; }
39 |
40 | public double? chaosValue { get; set; }
41 | public double? chaosEquivalent { get; set; }
42 |
43 | public bool corrupted { get; set; }
44 | public int gemLevel { get; set; }
45 | public int gemQuality { get; set; }
46 |
47 | public double GetValue() {
48 | return chaosValue ?? chaosEquivalent ?? 0;
49 | }
50 | }
51 |
52 | ///
53 | /// Eye-candy for pretty code
54 | ///
55 | public sealed class PoeNinjasEntryDict {
56 | public List lines { get; set; }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/EasyBuyout/Prices/PriceManager.cs:
--------------------------------------------------------------------------------
1 | using EasyBuyout.Settings;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net;
5 | using System.Threading;
6 | using System.Web.Script.Serialization;
7 | using EasyBuyout.Item;
8 |
9 | namespace EasyBuyout.Prices {
10 | ///
11 | /// Handles downloading, managing and translating price data from various websites
12 | ///
13 | public class PriceManager {
14 | private readonly JavaScriptSerializer _javaScriptSerializer;
15 | private readonly WebClient _webClient;
16 | private readonly Action _log;
17 | private readonly Dictionary _entryMap;
18 | private readonly Config _config;
19 | private Timer _liveUpdateTask;
20 |
21 | ///
22 | /// Constructor
23 | ///
24 | ///
25 | ///
26 | ///
27 | public PriceManager(Config config, WebClient webClient, Action log) {
28 | _config = config;
29 | _webClient = webClient;
30 | _log = log;
31 |
32 | _javaScriptSerializer = new JavaScriptSerializer {MaxJsonLength = int.MaxValue};
33 | _entryMap = new Dictionary();
34 | }
35 |
36 | //-----------------------------------------------------------------------------------------------------------
37 | // Data download
38 | //-----------------------------------------------------------------------------------------------------------
39 |
40 | ///
41 | /// Picks download source depending on source selection
42 | ///
43 | public void Download() {
44 | if (_config.SelectedLeague == null) {
45 | return;
46 | }
47 |
48 | _entryMap.Clear();
49 | foreach (var api in _config.Source.SourceApis) {
50 | foreach (var category in api.Categories) {
51 | _log($"Fetching: {category}", MainWindow.Flair.Info);
52 |
53 | try {
54 | var url = api.Url.Replace("{league}", _config.SelectedLeague).Replace("{category}", category);
55 | var jsonString = _webClient.DownloadString(url);
56 |
57 | // Deserialize
58 | var entryDict = _javaScriptSerializer.Deserialize(jsonString);
59 |
60 | if (entryDict == null) {
61 | _log($"[{_config.SelectedLeague}] Reply was null for {category}", MainWindow.Flair.Error);
62 | break;
63 | }
64 |
65 | // Add all entries
66 | foreach (var line in entryDict.lines) {
67 | var key = new Key(category, line);
68 |
69 | if (_entryMap.ContainsKey(key)) {
70 | Console.WriteLine($"Duplicate key for {key}");
71 | continue;
72 | }
73 |
74 | _entryMap.Add(new Key(category, line), new Entry(line.GetValue(), line.count));
75 | }
76 | } catch (Exception ex) {
77 | _log(ex.ToString(), MainWindow.Flair.Error);
78 | }
79 | }
80 | }
81 |
82 | _liveUpdateTask?.Dispose();
83 | if (_config.FlagLiveUpdate) {
84 | _liveUpdateTask = new Timer(LiveUpdate, null, _config.LiveUpdateDelayMs, _config.LiveUpdateDelayMs);
85 | }
86 | }
87 |
88 | ///
89 | /// Periodically downloads price data
90 | ///
91 | /// Literally null
92 | private void LiveUpdate(object state) {
93 | if (!_config.FlagLiveUpdate) {
94 | return;
95 | }
96 |
97 | _log("Updating prices", MainWindow.Flair.Info);
98 | Download();
99 | _log("Prices updated", MainWindow.Flair.Info);
100 | }
101 |
102 | //-----------------------------------------------------------------------------------------------------------
103 | // Generic methods
104 | //-----------------------------------------------------------------------------------------------------------
105 |
106 | ///
107 | /// Get Entry instances associated with provided keys.
108 | /// Objects are ordered as the provided keys
109 | ///
110 | ///
111 | /// List of Entry objects
112 | public Entry GetEntry(Key key) {
113 | _entryMap.TryGetValue(key, out var entry);
114 | return entry;
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/EasyBuyout/Prices/PriceboxWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/EasyBuyout/Prices/PriceboxWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Input;
3 |
4 | namespace EasyBuyout.Prices {
5 | ///
6 | /// Interaction logic for PriceBox.xaml
7 | ///
8 | public partial class PriceboxWindow : Window {
9 | public PriceboxWindow() {
10 | InitializeComponent();
11 | }
12 |
13 | ///
14 | /// Hide the box when user moves cursor aways
15 | ///
16 | ///
17 | ///
18 | private void Window_MouseLeave(object sender, MouseEventArgs e) {
19 | Hide();
20 | }
21 |
22 | ///
23 | /// Set the position of the price overlay under the user's cursor
24 | ///
25 | public void SetPosition() {
26 | Left = System.Windows.Forms.Cursor.Position.X - Width / 2;
27 | Top = System.Windows.Forms.Cursor.Position.Y - Height / 2;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/EasyBuyout/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("EasyBuyout")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("EasyBuyout")]
15 | [assembly: AssemblyCopyright("Copyright © 2018")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/EasyBuyout/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace EasyBuyout.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EasyBuyout.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/EasyBuyout/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/EasyBuyout/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace EasyBuyout.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/EasyBuyout/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/EasyBuyout/Settings/Config.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using EasyBuyout.Settings.Source;
3 |
4 | namespace EasyBuyout.Settings {
5 | public class Config {
6 | //-----------------------------------------------------------------------------------------------------------
7 | // Readonly
8 | //-----------------------------------------------------------------------------------------------------------
9 |
10 | public readonly SourceSite Source;
11 | public readonly string ProgramTitle = "EasyBuyout";
12 | public readonly string ProgramVersion = "v1.1.2";
13 |
14 | public readonly string LeagueApiUrl = "https://www.pathofexile.com/api/trade/data/leagues";
15 | public readonly string ManualLeagueDisplay = "";
16 | public readonly string GithubReleaseApi = "https://api.github.com/repos/siegrest/EasyBuyout/releases";
17 | public readonly int LiveUpdateDelayMs = 600000; // Update prices every x milliseconds
18 |
19 | //-----------------------------------------------------------------------------------------------------------
20 | // Dynamic
21 | //-----------------------------------------------------------------------------------------------------------
22 |
23 | public string NotePrefix { get; set; } = "~b/o";
24 | public string SelectedLeague { get; set; }
25 | public int LowerPricePercentage { get; set; } = 0;
26 | public int PasteDelay { get; set; } = 120;
27 | public int ClipboardWriteDelay { get; set; } = 4;
28 | public int PricePrecision { get; set; } = 1;
29 |
30 | public bool FlagSendNote { get; set; } = true;
31 | public bool FlagSendEnter { get; set; } = false;
32 | public bool FlagShowOverlay { get; set; } = false;
33 | public bool FlagLiveUpdate { get; set; } = true;
34 | public bool FlagRun { get; set; } = false;
35 |
36 | public Config() {
37 | // Initialize data sources
38 | Source = new SourceSite() {
39 | Name = "Poe.Ninja",
40 | SourceApis = new List() {
41 | new SourceApi() {
42 | Display = "Currency",
43 | Url = "https://poe.ninja/api/data/currencyoverview?league={league}&type={category}",
44 | Categories = new List() {
45 | "Currency", "Fragment"
46 | }
47 | },
48 | new SourceApi() {
49 | Display = "Items",
50 | Url = "https://poe.ninja/api/data/itemoverview?league={league}&type={category}",
51 | Categories = new List() {
52 | "UniqueArmour", "Essence", "DivinationCard", "Prophecy", "UniqueMap",
53 | "Map", "UniqueJewel", "UniqueFlask", "UniqueWeapon", "UniqueAccessory", "SkillGem"
54 | }
55 | }
56 | }
57 | };
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/EasyBuyout/Settings/ManualLeagueWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/EasyBuyout/Settings/ManualLeagueWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace EasyBuyout.Settings {
4 | ///
5 | /// Interaction logic for ManualLeagueWindow.xaml
6 | ///
7 | public partial class ManualLeagueWindow : Window {
8 | public string input;
9 |
10 | ///
11 | /// Constructor
12 | ///
13 | public ManualLeagueWindow() {
14 | InitializeComponent();
15 | }
16 |
17 | ///
18 | /// Apply button event
19 | ///
20 | ///
21 | ///
22 | private void Button_Apply_Click(object sender, RoutedEventArgs e) {
23 | input = TextBox_League.Text;
24 | Hide();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/EasyBuyout/Settings/SettingsWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/EasyBuyout/Settings/SettingsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 |
4 | namespace EasyBuyout.Settings {
5 | ///
6 | /// Interaction logic for SettingsWindow.xaml
7 | ///
8 | public partial class SettingsWindow {
9 | private readonly Config _config;
10 | private readonly Action _log;
11 |
12 | ///
13 | /// Constructor
14 | ///
15 | ///
16 | ///
17 | public SettingsWindow(Config config, Action log) {
18 | _config = config;
19 | _log = log;
20 |
21 | // Initialize the UI components
22 | InitializeComponent();
23 |
24 | // Add initial values to PricePrecision dropdown
25 | for (int i = 0; i < 4; i++) {
26 | ComboBox_PricePrecision.Items.Add(i);
27 | }
28 |
29 | ComboBox_PricePrecision.SelectedIndex = 0;
30 |
31 | // Set window options to default values
32 | ResetOptions();
33 | }
34 |
35 | ///
36 | /// Reverts all settings back to original state when cancel button is pressed
37 | ///
38 | private void ResetOptions() {
39 | ComboBox_PricePrecision.SelectedValue = _config.PricePrecision;
40 |
41 | // Reset text fields
42 | TextBox_Delay.Text = _config.PasteDelay.ToString();
43 | TextBox_LowerPrice.Text = _config.LowerPricePercentage.ToString();
44 |
45 | // Reset checkbox states
46 | CheckBox_SendEnter.IsChecked = _config.FlagSendEnter;
47 | Radio_SendNote.IsChecked = _config.FlagSendNote;
48 | Radio_ShowOverlay.IsChecked = _config.FlagShowOverlay;
49 | CheckBox_LiveUpdate.IsChecked = _config.FlagLiveUpdate;
50 |
51 | // Reset ~b/o radio states
52 | var tmp = _config.NotePrefix == (string) Radio_Buyout.Content;
53 | Radio_Buyout.IsChecked = tmp;
54 | Radio_Price.IsChecked = !tmp;
55 |
56 | // Reset enabled states
57 | CheckBox_SendEnter.IsEnabled = _config.FlagSendNote;
58 | Radio_Buyout.IsEnabled = _config.FlagSendNote;
59 | Radio_Price.IsEnabled = _config.FlagSendNote;
60 | TextBox_Delay.IsEnabled = _config.FlagSendNote;
61 | }
62 |
63 | //-----------------------------------------------------------------------------------------------------------
64 | // WPF events
65 | //-----------------------------------------------------------------------------------------------------------
66 |
67 | ///
68 | /// Intercepts window close event and hides it instead
69 | ///
70 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
71 | ResetOptions();
72 | e.Cancel = true;
73 | Hide();
74 | }
75 |
76 | ///
77 | /// Verifies current settings and saves them
78 | ///
79 | private void Button_Apply_Click(object sender, RoutedEventArgs e) {
80 | // Delay box
81 | int.TryParse(TextBox_Delay.Text, out var newPasteDelay);
82 | if (newPasteDelay != _config.PasteDelay) {
83 | if (newPasteDelay < 1 || newPasteDelay > 1000) {
84 | _log("Invalid input - delay (allowed: 1 - 1000)", MainWindow.Flair.Warn);
85 | TextBox_Delay.Text = _config.PasteDelay.ToString();
86 | } else {
87 | _log($"Changed delay {_config.PasteDelay} -> {newPasteDelay}", MainWindow.Flair.Info);
88 | _config.PasteDelay = newPasteDelay;
89 | }
90 | }
91 |
92 | // Lower price % box
93 | int.TryParse(TextBox_LowerPrice.Text, out var newLowerPercentage);
94 | if (newLowerPercentage != _config.LowerPricePercentage) {
95 | if (newLowerPercentage < 0 || newLowerPercentage > 100) {
96 | _log("Invalid input - percentage (allowed: 0 - 100)", MainWindow.Flair.Warn);
97 | TextBox_LowerPrice.Text = _config.LowerPricePercentage.ToString();
98 | } else {
99 | _log($"Changed percentage {_config.LowerPricePercentage} -> {newLowerPercentage}",
100 | MainWindow.Flair.Info);
101 | _config.LowerPricePercentage = newLowerPercentage;
102 | }
103 | }
104 |
105 | // Dropdowns
106 | if (_config.PricePrecision != (int) ComboBox_PricePrecision.SelectedValue) {
107 | _log($"Changed price precision {_config.PricePrecision} -> {ComboBox_PricePrecision.SelectedValue}",
108 | MainWindow.Flair.Info);
109 | _config.PricePrecision = (int) ComboBox_PricePrecision.SelectedValue;
110 | }
111 |
112 | // Checkboxes
113 | _config.FlagShowOverlay = Radio_ShowOverlay.IsChecked ?? false;
114 | _config.FlagSendEnter = CheckBox_SendEnter.IsChecked ?? false;
115 | _config.FlagSendNote = Radio_SendNote.IsChecked ?? false;
116 | _config.FlagLiveUpdate = CheckBox_LiveUpdate.IsChecked ?? false;
117 |
118 | // Radio buttons
119 | _config.NotePrefix = Radio_Buyout.IsChecked != null && (bool) Radio_Buyout.IsChecked
120 | ? Radio_Buyout.Content.ToString()
121 | : Radio_Price.Content.ToString();
122 |
123 | Hide();
124 | }
125 |
126 | ///
127 | /// Cancel button handler
128 | ///
129 | private void Button_Cancel_Click(object sender, RoutedEventArgs e) {
130 | ResetOptions();
131 | Hide();
132 | }
133 |
134 | ///
135 | /// Enables/disables other controls based on checkbox
136 | ///
137 | private void Radio_ShowOverlay_Click(object sender, RoutedEventArgs e) {
138 | CheckBox_SendEnter.IsEnabled = false;
139 | Radio_Buyout.IsEnabled = false;
140 | Radio_Price.IsEnabled = false;
141 | TextBox_Delay.IsEnabled = false;
142 | }
143 |
144 | ///
145 | /// Enables/disables other controls based on checkbox
146 | ///
147 | private void Radio_SendNote_Click(object sender, RoutedEventArgs e) {
148 | CheckBox_SendEnter.IsEnabled = true;
149 | Radio_Buyout.IsEnabled = true;
150 | Radio_Price.IsEnabled = true;
151 | TextBox_Delay.IsEnabled = true;
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/EasyBuyout/Settings/Source/SourceApi.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace EasyBuyout.Settings.Source {
4 | public class SourceApi {
5 | public string Display, Url;
6 | public List Categories;
7 | }
8 | }
--------------------------------------------------------------------------------
/EasyBuyout/Settings/Source/SourceSite.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace EasyBuyout.Settings.Source {
4 | public class SourceSite {
5 | public string Name;
6 | public List SourceApis;
7 | }
8 | }
--------------------------------------------------------------------------------
/EasyBuyout/Updater/Deserializers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EasyBuyout.Updater {
8 | ///
9 | /// Deserializer for Github's release API
10 | ///
11 | public sealed class ReleaseEntry {
12 | // Releases page
13 | public string html_url { get; set; }
14 | // Version tag
15 | public string tag_name { get; set; }
16 | // Release name
17 | public string name { get; set; }
18 | // Patch notes
19 | public string body { get; set; }
20 | // Attached files
21 | public List assets { get; set; }
22 | }
23 |
24 | ///
25 | /// Deserializer for Github's release API's assets
26 | ///
27 | public sealed class Asset {
28 | // Attachment's filename
29 | public string name { get; set; }
30 | // Size in bytes
31 | public string size { get; set; }
32 | // Direct download url
33 | public string browser_download_url { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/EasyBuyout/Updater/UpdateWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Github
26 |
27 |
28 | Github
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/EasyBuyout/Updater/UpdateWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Web.Script.Serialization;
5 | using EasyBuyout.Settings;
6 |
7 | namespace EasyBuyout.Updater {
8 | ///
9 | /// Interaction logic for UpdateWindow.xaml
10 | ///
11 | public partial class UpdateWindow {
12 | private readonly Action _log;
13 | private readonly WebClient _webClient;
14 | private readonly Config _config;
15 |
16 | ///
17 | /// Constructor
18 | ///
19 | ///
20 | ///
21 | ///
22 | public UpdateWindow(Config config, WebClient webClient, Action log) {
23 | _webClient = webClient;
24 | _log = log;
25 | _config = config;
26 | InitializeComponent();
27 | }
28 |
29 | ///
30 | /// Get latest release and show updater window if version is newer
31 | ///
32 | public void Run() {
33 | var releaseEntries = DownloadReleaseList();
34 | if (releaseEntries == null) {
35 | _log("Error checking new releases", MainWindow.Flair.Error);
36 | return;
37 | }
38 |
39 | // Compare versions of all releaseEntries and get newer ones
40 | var newReleases = CompareVersions(releaseEntries);
41 |
42 | // If there was a newer version available
43 | if (newReleases.Count <= 0) return;
44 |
45 | _log("New version available", MainWindow.Flair.Info);
46 |
47 | var patchNotes = "";
48 | foreach (var releaseEntry in newReleases) {
49 | patchNotes += $"{releaseEntry.tag_name}\n{releaseEntry.body}\n\n";
50 | }
51 |
52 | // Update UpdateWindow's elements
53 | Dispatcher.Invoke(() => {
54 | Label_NewVersion.Content = newReleases[0].tag_name;
55 | Label_CurrentVersion.Content = _config.ProgramVersion;
56 |
57 | HyperLink_URL.NavigateUri = new Uri(newReleases[0].html_url);
58 | HyperLink_URL_Direct.NavigateUri = new Uri(newReleases[0].assets[0].browser_download_url);
59 |
60 | TextBox_PatchNotes.AppendText(patchNotes);
61 |
62 | ShowDialog();
63 | });
64 | }
65 |
66 | ///
67 | /// Downloads releases from Github
68 | ///
69 | /// List of ReleaseEntry objects or null on failure
70 | private List DownloadReleaseList() {
71 | // WebClient can only handle one connection per instance.
72 | // It is bad practice to have multiple WebClients.
73 | while (_webClient.IsBusy) System.Threading.Thread.Sleep(10);
74 |
75 | try {
76 | var jsonString = _webClient.DownloadString(_config.GithubReleaseApi);
77 | return new JavaScriptSerializer().Deserialize>(jsonString);
78 | } catch (Exception ex) {
79 | _log(ex.ToString(), MainWindow.Flair.Error);
80 | return null;
81 | }
82 | }
83 |
84 | ///
85 | /// Compares provided entryList's release versions against own version
86 | ///
87 | /// Filtered (or empty) list of ReleaseEntry objects that are newer than own version
88 | private List CompareVersions(List releaseEntries) {
89 | var returnEntries = new List(releaseEntries.Count);
90 |
91 | foreach (var releaseEntry in releaseEntries) {
92 | var splitNew = releaseEntry.tag_name.Substring(1).Split('.');
93 | var splitOld = _config.ProgramVersion.Substring(1).Split('.');
94 |
95 | var minLen = splitNew.Length > splitOld.Length ? splitOld.Length : splitNew.Length;
96 |
97 | for (var i = 0; i < minLen; i++) {
98 | int.TryParse(splitNew[i], out var newVer);
99 | int.TryParse(splitOld[i], out var oldVer);
100 |
101 | if (newVer > oldVer) {
102 | returnEntries.Add(releaseEntry);
103 | break;
104 | }
105 |
106 | if (newVer < oldVer) {
107 | break;
108 | }
109 | }
110 | }
111 |
112 | return returnEntries;
113 | }
114 |
115 | //-----------------------------------------------------------------------------------------------------------
116 | // Events
117 | //-----------------------------------------------------------------------------------------------------------
118 |
119 | ///
120 | /// Opens up the web browser when URL is clicked
121 | ///
122 | ///
123 | ///
124 | private void HyperLink_URL_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
125 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri));
126 | e.Handled = true;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EasyBuyout for Path of Exile
2 |
3 | 
4 |
5 | ## What does it do
6 | Set a buyout notes on items with right click based on prices from [poe.ninja](http://poe.ninja). This handy program instantly outputs a buyout note containing the average price, allowing you to price hundreds of items without the tedious work of looking them up individually.
7 |
8 | ## Who is it for
9 | Be you a labrunner or an Uber Atziri farmer, who has dozens upon dozens of gems or maps that need prices or maybe just your average player who wants to actually enjoy the game instead of playing Shoppe Keep all day long. Well, then this program is perfect for you.
10 |
11 | ## Settings explained (as of v1.0.14)
12 | * `Show overlay` Displays a small TradeMacro-like box under the cursor that contains the price instead of pasting it (can be closed by moving the cursor away from the box)
13 | * `Send note` copies a buyout note (eg. `~b/o 5 chaos`) to your clipboard and pastes it into the game's note field
14 | * `Send enter` automatically presses enter after pasting the note, applying the price instantly
15 | * `Paste delay` delay in milliseconds between clicking and pasting the buyout note. Required as the premium stash's price menu takes some time to open. If the buyout notes are not being pasted, try incrementing this value by 50 until you see a change.
16 | * `Lower price by #%` takes whatever price the item usually goes for and reduces its price by that percentage
17 | * `Live update` Automatically downloads fresh prices every 10 minutes
18 | * `Price precision` Rounds prices. For example, precision 2 would mean `5.43262` -> `5.43`
19 |
20 | ## What can it price
21 | Armours, weapons, accessories, flasks, 6-links, 5-links, jewels, gems, divination cards, maps, currency, prophecies, essences, you name it. In short: if it's an item, this program can find a price for it.
22 |
23 | ## Words of warning
24 | * The prices might not be always 100% accurate because they are from poe.ninja
25 | * PriceBoxes probably don't work well with full-screen mode
26 | * Prices in hardcore are generally not accurate as nobody plays there
27 | * There might be some issues running this program alongside TradeMacro or similar applications as they both need to access the clipboard
28 |
--------------------------------------------------------------------------------
/screenshots/action.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibbydev/EasyBuyout/4a1874f97f22f52e57c195f4f8ea0a0bf8abc429/screenshots/action.gif
--------------------------------------------------------------------------------