civitai-ruby 0.1.0.pre1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9f71d8cde85129801aaa115cc63551ac8d1c1522358d061e5ab118bc6d46fbe3
4
+ data.tar.gz: 716f927d20dccf9e3414014d9b8fe0b46be747c5806d428c4bdd243b4f03ceec
5
+ SHA512:
6
+ metadata.gz: 4fdeb6dae5c62c86c17799a585a86aff332e917a3cbb3c8376ae3a215390464db145a2b5cf5697f4f05dc9bf19601b24337a09c45ccc662914bf6c76269871de
7
+ data.tar.gz: 239dfe9cbff1d5253dd3b2dac8d10acdff0ea0026d06154076ffed8eec8e75430b17527a1f88fffa7a8a613bb57b149e28b63a053418381c788c84725ac7943e
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+ require "json"
5
+ require "fileutils"
6
+
7
+ module Civitai
8
+ # Ruby client for the CivitAI REST API.
9
+ #
10
+ # @example
11
+ # client = Civitai::Client.new(api_key: "your_key")
12
+ # client.model(12345)
13
+ # client.search("mecha", type: "LORA", base_model: "SDXL 1.0")
14
+ # client.download(version_id: 67890, output: "/path/to/models/")
15
+ #
16
+ class Client
17
+ attr_reader :api_key
18
+
19
+ # @param api_key [String, nil] CivitAI API key (optional, enables higher rate limits)
20
+ # @param timeout [Integer] HTTP timeout in seconds
21
+ def initialize(api_key: nil, timeout: 30)
22
+ @api_key = api_key || ENV["CIVITAI_API_KEY"]
23
+ @timeout = timeout
24
+ end
25
+
26
+ # ----------------------------------------------------------------
27
+ # Model queries
28
+ # ----------------------------------------------------------------
29
+
30
+ # Fetch a model by ID.
31
+ # @param model_id [Integer]
32
+ # @return [Hash]
33
+ def model(model_id)
34
+ get("/models/#{model_id}")
35
+ end
36
+
37
+ # Fetch a model version by ID.
38
+ # @param version_id [Integer]
39
+ # @return [Hash]
40
+ def model_version(version_id)
41
+ get("/model-versions/#{version_id}")
42
+ end
43
+
44
+ # Fetch a model version by file SHA256 hash.
45
+ # @param hash [String] SHA256 hash
46
+ # @return [Hash]
47
+ def by_hash(hash)
48
+ get("/model-versions/by-hash/#{hash}")
49
+ end
50
+
51
+ # ----------------------------------------------------------------
52
+ # Search
53
+ # ----------------------------------------------------------------
54
+
55
+ # Search CivitAI models.
56
+ #
57
+ # @param query [String, nil] search query
58
+ # @param type [String, nil] model type: Checkpoint, LORA, TextualInversion, etc.
59
+ # @param base_model [String, nil] base model: "SD 1.5", "SDXL 1.0", "Pony", etc.
60
+ # @param sort [String] sort order: "Highest Rated", "Most Downloaded", "Newest"
61
+ # @param limit [Integer] max results (1-100)
62
+ # @param period [String, nil] time period: "Day", "Week", "Month", "Year", "AllTime"
63
+ # @param nsfw [Boolean, nil] nil = include all, true = only nsfw, false = exclude nsfw
64
+ # @param tag [String, nil] filter by tag
65
+ # @param username [String, nil] filter by creator
66
+ # @param page [Integer, nil] page number
67
+ # @return [Hash] with "items" array and "metadata" pagination
68
+ def search(
69
+ query = nil,
70
+ type: nil,
71
+ base_model: nil,
72
+ sort: "Most Downloaded",
73
+ limit: 20,
74
+ period: nil,
75
+ nsfw: nil,
76
+ tag: nil,
77
+ username: nil,
78
+ page: nil
79
+ )
80
+ params = build_search_params(
81
+ query: query, type: type, base_model: base_model,
82
+ sort: sort, limit: limit, period: period, nsfw: nsfw,
83
+ tag: tag, username: username, page: page
84
+ )
85
+
86
+ has_filters = !type.nil? || !base_model.nil? || !tag.nil?
87
+ result = get("/models", **params)
88
+ filter_results(result, query, has_filters, limit)
89
+ end
90
+
91
+ # ----------------------------------------------------------------
92
+ # Download
93
+ # ----------------------------------------------------------------
94
+
95
+ # Download a model file.
96
+ #
97
+ # @param version_id [Integer, nil] model version ID
98
+ # @param model_id [Integer, nil] model ID (downloads latest version)
99
+ # @param output [String] output directory or file path
100
+ # @param on_progress [Proc, nil] callback(downloaded_bytes, total_bytes)
101
+ # @return [String] path to downloaded file
102
+ def download(version_id: nil, model_id: nil, output: ".", on_progress: nil)
103
+ raise ArgumentError, "Provide version_id or model_id" unless version_id || model_id
104
+
105
+ unless version_id
106
+ m = model(model_id)
107
+ version_id = m.dig("modelVersions", 0, "id")
108
+ raise NotFoundError, "No versions found for model #{model_id}" unless version_id
109
+ end
110
+
111
+ url = "#{DOWNLOAD_BASE}/#{version_id}"
112
+ download_file(url, output, on_progress: on_progress)
113
+ end
114
+
115
+ private
116
+
117
+ def http
118
+ client = HTTP
119
+ .headers("Accept" => "application/json")
120
+ .headers("User-Agent" => "civitai-ruby/#{Civitai::VERSION}")
121
+ .timeout(@timeout)
122
+ .follow(max_hops: 5)
123
+
124
+ client = client.auth("Bearer #{@api_key}") if @api_key
125
+ client
126
+ end
127
+
128
+ def get(path, **params)
129
+ url = "#{API_BASE}#{path}"
130
+ response = http.get(url, params: params)
131
+
132
+ case response.status.to_i
133
+ when 200
134
+ JSON.parse(response.body.to_s)
135
+ when 404
136
+ raise NotFoundError, "Not found: #{path}"
137
+ when 429
138
+ raise RateLimitError, "CivitAI rate limit exceeded"
139
+ else
140
+ raise APIError, "CivitAI API error: #{response.status}"
141
+ end
142
+ end
143
+
144
+ def build_search_params(query:, type:, base_model:, sort:, limit:, period:, nsfw:, tag:, username:, page:)
145
+ params = {limit: [limit, 100].min, sort: sort}
146
+ params[:nsfw] = nsfw.nil? ? "true" : nsfw.to_s
147
+
148
+ has_filters = !type.nil? || !base_model.nil? || !tag.nil?
149
+ params[:query] = query if query && !has_filters
150
+ params[:types] = type if type
151
+ params[:baseModels] = base_model if base_model
152
+ params[:period] = period if period
153
+ params[:tag] = tag if tag
154
+ params[:username] = username if username
155
+ params[:page] = page if page && page > 1
156
+ params[:limit] = 100 if query && has_filters
157
+
158
+ params
159
+ end
160
+
161
+ def filter_results(result, query, has_filters, limit)
162
+ return result unless query && has_filters
163
+
164
+ q_lower = query.downcase
165
+ items = (result["items"] || []).select { |m| m["name"]&.downcase&.include?(q_lower) }
166
+ result.merge("items" => items.first(limit))
167
+ end
168
+
169
+ def download_file(url, output, on_progress: nil)
170
+ output_path = File.directory?(output) ? output : File.dirname(output)
171
+ FileUtils.mkdir_p(output_path)
172
+
173
+ dl_client = HTTP
174
+ .headers("User-Agent" => "civitai-ruby/#{Civitai::VERSION}")
175
+ .follow(max_hops: 5)
176
+ dl_client = dl_client.auth("Bearer #{@api_key}") if @api_key
177
+
178
+ response = dl_client.get(url)
179
+
180
+ # Extract filename from Content-Disposition
181
+ cd = response.headers["Content-Disposition"]
182
+ filename = if cd && cd =~ /filename="?([^";\s]+)"?/
183
+ $1
184
+ else
185
+ "model_#{Time.now.to_i}.safetensors"
186
+ end
187
+
188
+ dest = File.directory?(output) ? File.join(output, filename) : output
189
+ total = response.headers["Content-Length"]&.to_i || 0
190
+ downloaded = 0
191
+
192
+ File.open(dest, "wb") do |file|
193
+ response.body.each do |chunk|
194
+ file.write(chunk)
195
+ downloaded += chunk.bytesize
196
+ on_progress&.call(downloaded, total)
197
+ end
198
+ end
199
+
200
+ dest
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Civitai
4
+ VERSION = "0.1.0.pre1"
5
+ end
data/lib/civitai.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require_relative "civitai/version"
6
+ require_relative "civitai/client"
7
+
8
+ module Civitai
9
+ API_BASE = "https://civitai.com/api/v1"
10
+ DOWNLOAD_BASE = "https://civitai.com/api/download/models"
11
+
12
+ class Error < StandardError; end
13
+ class NotFoundError < Error; end
14
+ class APIError < Error; end
15
+ class RateLimitError < Error; end
16
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: civitai-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre1
5
+ platform: ruby
6
+ authors:
7
+ - aladac
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ description: Search, browse, and download AI models from CivitAI. Supports model lookup
28
+ by ID/hash, search with filters, and streaming downloads with resume.
29
+ email:
30
+ - aladac@saiden.dev
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/civitai.rb
36
+ - lib/civitai/client.rb
37
+ - lib/civitai/version.rb
38
+ homepage: https://github.com/aladac/civitai-ruby
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ homepage_uri: https://github.com/aladac/civitai-ruby
43
+ source_code_uri: https://github.com/aladac/civitai-ruby
44
+ changelog_uri: https://github.com/aladac/civitai-ruby/blob/main/CHANGELOG.md
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 3.2.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.5.22
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Ruby client for the CivitAI API
64
+ test_files: []