htb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTB
4
+ class Machines
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # List all machines
10
+ # GET /api/v4/machine/list
11
+ def list
12
+ @client.get("/machine/list")
13
+ end
14
+
15
+ # Get machine profile by ID or name
16
+ # GET /api/v4/machine/profile/{id_or_name}
17
+ def profile(id_or_name)
18
+ @client.get("/machine/profile/#{id_or_name}")
19
+ end
20
+
21
+ # Get active machine
22
+ # GET /api/v4/machine/active
23
+ def active
24
+ @client.get("/machine/active")
25
+ end
26
+
27
+ # Play/start a machine (free VPN users)
28
+ # POST /api/v4/machine/play/{machine_id}
29
+ def play(machine_id)
30
+ @client.post("/machine/play/#{machine_id}")
31
+ end
32
+
33
+ # Stop a machine
34
+ # POST /api/v4/machine/stop
35
+ def stop
36
+ @client.post("/machine/stop")
37
+ end
38
+
39
+ # Submit flag for machine ownership
40
+ # POST /api/v4/machine/own
41
+ # @param machine_id [Integer] Machine ID
42
+ # @param flag [String] The flag to submit
43
+ # @param difficulty [Integer] Difficulty rating (10-100)
44
+ def own(machine_id:, flag:, difficulty: 50)
45
+ @client.post("/machine/own", {
46
+ id: machine_id,
47
+ flag: flag,
48
+ difficulty: difficulty
49
+ })
50
+ end
51
+
52
+ # Submit user flag
53
+ # POST /api/v4/machine/own/user/{machine_id}
54
+ def own_user(machine_id, flag:, difficulty: 50)
55
+ @client.post("/machine/own/user/#{machine_id}", {
56
+ flag: flag,
57
+ difficulty: difficulty
58
+ })
59
+ end
60
+
61
+ # Submit root flag
62
+ # POST /api/v4/machine/own/root/{machine_id}
63
+ def own_root(machine_id, flag:, difficulty: 50)
64
+ @client.post("/machine/own/root/#{machine_id}", {
65
+ flag: flag,
66
+ difficulty: difficulty
67
+ })
68
+ end
69
+
70
+ # Get machine activity
71
+ # GET /api/v4/machine/activity/{machine_id}
72
+ def activity(machine_id)
73
+ @client.get("/machine/activity/#{machine_id}")
74
+ end
75
+
76
+ # Get todo list for machine
77
+ # GET /api/v4/machine/todo/{machine_id}
78
+ def todo(machine_id)
79
+ @client.get("/machine/todo/#{machine_id}")
80
+ end
81
+
82
+ # Update todo status for machine
83
+ # POST /api/v4/machine/todo/update/{machine_id}
84
+ def update_todo(machine_id, flag_type:)
85
+ @client.post("/machine/todo/update/#{machine_id}", { flag_type: flag_type })
86
+ end
87
+
88
+ # Submit machine review
89
+ # POST /api/v4/machine/review
90
+ # @param machine_id [Integer] Machine ID
91
+ # @param stars [Integer] Star rating (1-5)
92
+ # @param headline [String] Review headline
93
+ # @param review [String] Review text
94
+ def review(machine_id:, stars:, headline:, review:)
95
+ @client.post("/machine/review", {
96
+ id: machine_id,
97
+ stars: stars,
98
+ headline: headline,
99
+ review: review
100
+ })
101
+ end
102
+
103
+ # Get recommended machines
104
+ # GET /api/v4/machine/recommended
105
+ def recommended
106
+ @client.get("/machine/recommended")
107
+ end
108
+
109
+ # Get spawned machines
110
+ # GET /api/v4/machine/spawned
111
+ def spawned
112
+ @client.get("/machine/spawned")
113
+ end
114
+
115
+ # Get retired machines
116
+ # GET /api/v4/machine/list/retired
117
+ def retired
118
+ @client.get("/machine/list/retired")
119
+ end
120
+
121
+ # Get starting point machines
122
+ # GET /api/v4/machine/list/sp
123
+ def starting_point
124
+ @client.get("/machine/list/sp")
125
+ end
126
+
127
+ # Get paginated machine list
128
+ # GET /api/v4/machine/paginated
129
+ def paginated(page: 1, per_page: 20, sort_by: nil, sort_type: nil)
130
+ params = { page: page, per_page: per_page }
131
+ params[:sort_by] = sort_by if sort_by
132
+ params[:sort_type] = sort_type if sort_type
133
+ @client.get("/machine/paginated", params)
134
+ end
135
+
136
+ # Search machines
137
+ # GET /api/v4/search/fetch?query={query}&tags=[]
138
+ def search(query, tags: [])
139
+ @client.get("/search/fetch", { query: query, tags: tags })
140
+ end
141
+ end
142
+ end
data/lib/htb/users.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTB
4
+ class Users
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # Get current user info
10
+ # GET /api/v4/user/info
11
+ def info
12
+ @client.get("/user/info")
13
+ end
14
+
15
+ # Get user profile by ID
16
+ # GET /api/v4/user/profile/basic/{user_id}
17
+ def profile(user_id)
18
+ @client.get("/user/profile/basic/#{user_id}")
19
+ end
20
+
21
+ # Get current user's profile
22
+ # GET /api/v4/user/profile/basic/me
23
+ def me
24
+ @client.get("/user/profile/basic/me")
25
+ end
26
+
27
+ # Get user's activity
28
+ # GET /api/v4/user/profile/activity/{user_id}
29
+ def activity(user_id)
30
+ @client.get("/user/profile/activity/#{user_id}")
31
+ end
32
+
33
+ # Get user's bloods (first solves)
34
+ # GET /api/v4/user/profile/bloods/{user_id}
35
+ def bloods(user_id)
36
+ @client.get("/user/profile/bloods/#{user_id}")
37
+ end
38
+
39
+ # Get user's progress
40
+ # GET /api/v4/user/profile/progress/machines/os/{user_id}
41
+ def machine_progress(user_id)
42
+ @client.get("/user/profile/progress/machines/os/#{user_id}")
43
+ end
44
+
45
+ # Get user's challenge progress
46
+ # GET /api/v4/user/profile/progress/challenges/{user_id}
47
+ def challenge_progress(user_id)
48
+ @client.get("/user/profile/progress/challenges/#{user_id}")
49
+ end
50
+
51
+ # Get user's content (writeups, etc.)
52
+ # GET /api/v4/user/profile/content/{user_id}
53
+ def content(user_id)
54
+ @client.get("/user/profile/content/#{user_id}")
55
+ end
56
+
57
+ # Update user settings
58
+ # PUT /api/v4/user/settings
59
+ def update_settings(settings)
60
+ @client.put("/user/settings", settings)
61
+ end
62
+
63
+ # Get user's connections (followers/following)
64
+ # GET /api/v4/user/profile/connections/{user_id}
65
+ def connections(user_id)
66
+ @client.get("/user/profile/connections/#{user_id}")
67
+ end
68
+
69
+ # Follow a user
70
+ # POST /api/v4/user/follow/{user_id}
71
+ def follow(user_id)
72
+ @client.post("/user/follow/#{user_id}")
73
+ end
74
+
75
+ # Unfollow a user
76
+ # DELETE /api/v4/user/follow/{user_id}
77
+ def unfollow(user_id)
78
+ @client.delete("/user/follow/#{user_id}")
79
+ end
80
+
81
+ # Get user's badges
82
+ # GET /api/v4/user/profile/badges/{user_id}
83
+ def badges(user_id)
84
+ @client.get("/user/profile/badges/#{user_id}")
85
+ end
86
+
87
+ # Get subscriptions info
88
+ # GET /api/v4/user/subscriptions
89
+ def subscriptions
90
+ @client.get("/user/subscriptions")
91
+ end
92
+
93
+ # Get user's rank history
94
+ # GET /api/v4/user/profile/graph/{period}/{user_id}
95
+ # period: 1M, 3M, 6M, 1Y
96
+ def rank_history(user_id, period: "1Y")
97
+ @client.get("/user/profile/graph/#{period}/#{user_id}")
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTB
4
+ VERSION = "0.1.0"
5
+ end
data/lib/htb/vm.rb ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTB
4
+ class VM
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # Spawn a machine (VIP/Starting Point)
10
+ # POST /api/v4/vm/spawn
11
+ # @param machine_id [Integer] The machine ID to spawn
12
+ # @return [Hash] Response containing spawn status
13
+ #
14
+ # @example
15
+ # client.vm.spawn(811)
16
+ # # => { "success" => "1", "message" => "Machine is being spawned..." }
17
+ #
18
+ # Note: This endpoint is used for VIP and Starting Point machines.
19
+ # The base URI changed from www.hackthebox.com to labs.hackthebox.com
20
+ def spawn(machine_id)
21
+ @client.post("/vm/spawn", { machine_id: machine_id })
22
+ end
23
+
24
+ # Terminate a running machine
25
+ # POST /api/v4/vm/terminate
26
+ # @param machine_id [Integer] The machine ID to terminate
27
+ def terminate(machine_id)
28
+ @client.post("/vm/terminate", { machine_id: machine_id })
29
+ end
30
+
31
+ # Reset a machine
32
+ # POST /api/v4/vm/reset
33
+ # @param machine_id [Integer] The machine ID to reset
34
+ def reset(machine_id)
35
+ @client.post("/vm/reset", { machine_id: machine_id })
36
+ end
37
+
38
+ # Extend machine time
39
+ # POST /api/v4/vm/extend
40
+ # @param machine_id [Integer] The machine ID to extend
41
+ def extend(machine_id)
42
+ @client.post("/vm/extend", { machine_id: machine_id })
43
+ end
44
+
45
+ # Get VM status
46
+ # GET /api/v4/vm/status
47
+ def status
48
+ @client.get("/vm/status")
49
+ end
50
+
51
+ # Get active VM
52
+ # GET /api/v4/vm/active
53
+ def active
54
+ @client.get("/vm/active")
55
+ end
56
+
57
+ # Transfer VM to another lab
58
+ # POST /api/v4/vm/transfer
59
+ def transfer(machine_id:, lab_id:)
60
+ @client.post("/vm/transfer", { machine_id: machine_id, lab_id: lab_id })
61
+ end
62
+ end
63
+ end
data/lib/htb/vpn.rb ADDED
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTB
4
+ class VPN
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # Get VPN server list
10
+ # GET /api/v4/connections/servers
11
+ def servers
12
+ @client.get("/connections/servers")
13
+ end
14
+
15
+ # Get current VPN connection status
16
+ # GET /api/v4/connections/status
17
+ def status
18
+ @client.get("/connections/status")
19
+ end
20
+
21
+ # Switch VPN server
22
+ # POST /api/v4/connections/servers/switch/{server_id}
23
+ def switch(server_id)
24
+ @client.post("/connections/servers/switch/#{server_id}")
25
+ end
26
+
27
+ # Regenerate VPN config
28
+ # POST /api/v4/connections/regenerate
29
+ def regenerate
30
+ @client.post("/connections/regenerate")
31
+ end
32
+
33
+ # Download VPN config
34
+ # GET /api/v4/access/ovpnfile/{server_id}/{tcp_or_udp}
35
+ # @param server_id [Integer] VPN server ID
36
+ # @param protocol [String] "tcp" or "udp" (default: "udp")
37
+ def download_config(server_id, protocol: "udp")
38
+ @client.get("/access/ovpnfile/#{server_id}/#{protocol}")
39
+ end
40
+
41
+ # Get labs list
42
+ # GET /api/v4/labs/list
43
+ def labs
44
+ @client.get("/labs/list")
45
+ end
46
+
47
+ # Get available labs for user
48
+ # GET /api/v4/labs/available
49
+ def available_labs
50
+ @client.get("/labs/available")
51
+ end
52
+
53
+ # Get lab info
54
+ # GET /api/v4/lab/info/{lab_id}
55
+ def lab_info(lab_id)
56
+ @client.get("/lab/info/#{lab_id}")
57
+ end
58
+
59
+ # Get prolabs list
60
+ # GET /api/v4/prolab/list
61
+ def prolabs
62
+ @client.get("/prolab/list")
63
+ end
64
+
65
+ # Get prolab info
66
+ # GET /api/v4/prolab/info/{prolab_id}
67
+ def prolab_info(prolab_id)
68
+ @client.get("/prolab/info/#{prolab_id}")
69
+ end
70
+
71
+ # Get fortress list
72
+ # GET /api/v4/fortress/list
73
+ def fortresses
74
+ @client.get("/fortress/list")
75
+ end
76
+
77
+ # Get endgame list
78
+ # GET /api/v4/endgame/list
79
+ def endgames
80
+ @client.get("/endgame/list")
81
+ end
82
+ end
83
+ end
data/lib/htb.rb ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+ require "json"
6
+
7
+ require_relative "htb/version"
8
+ require_relative "htb/config"
9
+ require_relative "htb/client"
10
+ require_relative "htb/machines"
11
+ require_relative "htb/users"
12
+ require_relative "htb/vm"
13
+ require_relative "htb/challenges"
14
+ require_relative "htb/vpn"
15
+
16
+ module HTB
17
+ class Error < StandardError; end
18
+ class AuthenticationError < Error; end
19
+ class RateLimitError < Error; end
20
+ class NotFoundError < Error; end
21
+
22
+ # Base URI for HTB API v4
23
+ # Note: The base URI changed from www.hackthebox.com to labs.hackthebox.com
24
+ BASE_URI = "https://labs.hackthebox.com"
25
+ API_VERSION = "v4"
26
+
27
+ class << self
28
+ attr_accessor :api_token
29
+
30
+ def configure
31
+ yield self
32
+ end
33
+
34
+ def client
35
+ @client ||= Client.new(api_token: api_token)
36
+ end
37
+
38
+ def reset_client!
39
+ @client = nil
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,225 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: htb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - usiegj00
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-multipart
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.12'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-spinner
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-prompt
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.23'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.23'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pastel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.8'
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '13.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '13.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.0'
167
+ description: A Ruby gem for interacting with the Hack The Box (HTB) API v4. Supports
168
+ machine management, user profiles, VM spawning, flag submission, and more.
169
+ email:
170
+ - usiegj00@users.noreply.github.com
171
+ executables:
172
+ - htb
173
+ extensions: []
174
+ extra_rdoc_files: []
175
+ files:
176
+ - ".rspec"
177
+ - CHANGELOG.md
178
+ - LICENSE.txt
179
+ - README.md
180
+ - Rakefile
181
+ - exe/htb
182
+ - htb.gemspec
183
+ - lib/htb.rb
184
+ - lib/htb/challenges.rb
185
+ - lib/htb/cli.rb
186
+ - lib/htb/cli/challenges.rb
187
+ - lib/htb/cli/machines.rb
188
+ - lib/htb/cli/main.rb
189
+ - lib/htb/cli/users.rb
190
+ - lib/htb/cli/vm.rb
191
+ - lib/htb/cli/vpn.rb
192
+ - lib/htb/client.rb
193
+ - lib/htb/config.rb
194
+ - lib/htb/machines.rb
195
+ - lib/htb/users.rb
196
+ - lib/htb/version.rb
197
+ - lib/htb/vm.rb
198
+ - lib/htb/vpn.rb
199
+ homepage: https://github.com/aluminumio/htb-ruby
200
+ licenses:
201
+ - MIT
202
+ metadata:
203
+ homepage_uri: https://github.com/aluminumio/htb-ruby
204
+ source_code_uri: https://github.com/aluminumio/htb-ruby
205
+ changelog_uri: https://github.com/aluminumio/htb-ruby/blob/main/CHANGELOG.md
206
+ post_install_message:
207
+ rdoc_options: []
208
+ require_paths:
209
+ - lib
210
+ required_ruby_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: 2.7.0
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ requirements: []
221
+ rubygems_version: 3.0.3.1
222
+ signing_key:
223
+ specification_version: 4
224
+ summary: Ruby client for the Hack The Box API v4
225
+ test_files: []