ordinals 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8713aa7b98dcb5356b762898b13ab4539313ef8621f2930b5291519edd759f50
4
- data.tar.gz: 6c4a9f720237c5e6468eede3f7b69988ecbdcf5fd347749cd9f18dd9de0fdb6a
3
+ metadata.gz: 23ab1c1b894f3d7d528d90943d890e07c40d878cc56b54206384be335ae47fa6
4
+ data.tar.gz: f3ca9276b04d13052424d9a4834204d7153a92681507efac9c8511949e6ec362
5
5
  SHA512:
6
- metadata.gz: 803d2f9357b35101f2572767de7f7176966d077b2b87bef45b2cb6c1f90c80ec4513d71aad231e08ddc5a112683ac0c24139a40e832f714dac05b47039654b3f
7
- data.tar.gz: 64071daa9c9c4352174a4be0c5cce8a7ee5d407920c922f29cadf8d015ab5ce963cc922f665039bfa3397142383aeeef3cff4e8c4650ec96faec32a33f6bddf3
6
+ metadata.gz: a91a05ebbaf2ab66030ef45bf9a8da6744df0f40d3014451364f1d802a0193c0a7450f90647612e95c6469b7c21a6f8c6f04a10e3a354d820cafa88de32ccc6b
7
+ data.tar.gz: 30ca5d81426d13f3149a37f9eb8a7bc3a703c22d9cf0685c991033273ae81efe4917fce3d00f68aadfaf9742ab0d626bdc6e290a2f52c8b2c967b0453ebaa475
data/Manifest.txt CHANGED
@@ -4,4 +4,7 @@ README.md
4
4
  Rakefile
5
5
  bin/ordbase
6
6
  lib/ordinals.rb
7
+ lib/ordinals/api.rb
7
8
  lib/ordinals/collection.rb
9
+ lib/ordinals/stats.rb
10
+ lib/ordinals/tool.rb
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ordinals
2
2
 
3
- ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery & helpers for Bitcoin & co.
3
+ ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery & helpers for Bitcoin, Litcoin, Dogecoin & co.
4
4
 
5
5
 
6
6
  * home :: [github.com/pixelartexchange/ordinals.sandbox](https://github.com/pixelartexchange/ordinals.sandbox)
@@ -9,7 +9,8 @@ ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery & help
9
9
  * rdoc :: [rubydoc.info/gems/ordinals](http://rubydoc.info/gems/ordinals)
10
10
 
11
11
 
12
- ## Comand-Line Usage
12
+
13
+ ## Command-Line Usage
13
14
 
14
15
  Let's use the 100 Ordinal Punks collection to try out the
15
16
  `ordbase` command-line tool shipping with the ordinals package.
@@ -165,6 +166,6 @@ Use it as you please with no restrictions whatsoever.
165
166
 
166
167
  ## Questions? Comments?
167
168
 
169
+ Post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
168
170
 
169
- Post them on the [D.I.Y. Punk (Pixel) Art reddit](https://old.reddit.com/r/DIYPunkArt). Thanks.
170
171
 
data/Rakefile CHANGED
@@ -10,9 +10,9 @@ end
10
10
 
11
11
  Hoe.spec 'ordinals' do
12
12
 
13
- self.version = '0.1.0'
13
+ self.version = '1.0.0'
14
14
 
15
- self.summary = 'ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery & helpers for Bitcoin & co.'
15
+ self.summary = 'ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery & helpers for Bitcoin, Litecoin, Dogecoin & co.'
16
16
  self.description = summary
17
17
 
18
18
  self.urls = { home: 'https://github.com/pixelartexchange/ordinals.sandbox' }
@@ -27,6 +27,7 @@ Hoe.spec 'ordinals' do
27
27
  self.extra_deps = [
28
28
  ['cocos'],
29
29
  ['pixelart'],
30
+ ['nokogiri'], ## required / used by api support (html parsing)
30
31
  ]
31
32
 
32
33
  self.licenses = ['Public Domain']
@@ -0,0 +1,224 @@
1
+
2
+
3
+ module Ordinals
4
+
5
+
6
+ class Api ## change/rename Api to Client - why? why not?
7
+ def self.litecoin
8
+ @litecoin ||= new( 'https://litecoin.earlyordies.com' )
9
+ @litecoin
10
+ end
11
+
12
+ def self.bitcoin
13
+ @bitcoin ||= new( 'https://ordinals.com' )
14
+ @bitcoin
15
+ end
16
+ ## todo: add ltc and btc alias - why? why not?
17
+
18
+ def self.dogecoin
19
+ ## note: "doginals" call inscriptions
20
+ ## shibescriptions
21
+ @dogecoin ||= new( 'https://doginals.com', inscription: 'shibescription' )
22
+ @dogecoin
23
+ end
24
+
25
+
26
+ def initialize( base, inscription: 'inscription' )
27
+ @base = base
28
+ @inscription = inscription
29
+ end
30
+
31
+
32
+ ###
33
+ # use a struct-like content class - why? why not?
34
+ class Content
35
+ attr_reader :data,
36
+ :type,
37
+ :length
38
+ def initialize( data, type, length )
39
+ @data = data
40
+ @type = type
41
+ @length = length
42
+ end
43
+
44
+ alias_method :blob, :data
45
+ end ## (nested) class Content
46
+
47
+
48
+
49
+ def content( id )
50
+ src = "#{@base}/content/#{id}"
51
+ res = get( src )
52
+
53
+ content_type = res.content_type
54
+ content_length = res.content_length
55
+
56
+ ## note - content_length -- returns an integer (number)
57
+ ## puts "content_length:"
58
+ ## print content_length.inspect
59
+ ## print " - #{content_length.class.name}\n"
60
+
61
+ content = Content.new(
62
+ res.blob,
63
+ content_type,
64
+ content_length )
65
+ content
66
+ end
67
+
68
+
69
+ =begin
70
+ <dl>
71
+ <dt>id</dt>
72
+ <dd class=monospace>d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2i0</dd>
73
+ <dt>address</dt>
74
+ <dd class=monospace>bc1pqapcyswesvccgqsmuncd96ylghs9juthqeshdr8smmh9w7azn8zsghjjar</dd>
75
+ <dt>output value</dt>
76
+ <dd>10000</dd>
77
+ <dt>sat</dt>
78
+ <dd><a href=/sat/1320953397332258>1320953397332258</a></dd>
79
+ <dt>preview</dt>
80
+ <dd><a href=/preview/d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2i0>link</a></dd>
81
+ <dt>content</dt>
82
+ <dd><a href=/content/d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2i0>link</a></dd>
83
+ <dt>content length</dt>
84
+ <dd>71997 bytes</dd>
85
+ <dt>content type</dt>
86
+ <dd>text/plain;charset=utf-8</dd>
87
+ <dt>timestamp</dt>
88
+ <dd><time>2023-02-11 21:39:00 UTC</time></dd>
89
+ <dt>genesis height</dt>
90
+ <dd><a href=/block/776090>776090</a></dd>
91
+ <dt>genesis fee</dt>
92
+ <dd>273630</dd>
93
+ <dt>genesis transaction</dt>
94
+ <dd><a class=monospace href=/tx/d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2>d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2</a></dd>
95
+ <dt>location</dt>
96
+ <dd class=monospace>d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2:0:0</dd>
97
+ <dt>output</dt>
98
+ <dd><a class=monospace href=/output/d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2:0>d026ac5994f698dba475681359b6c29d6d39a895484b95e06b7ae49921d80df2:0</a></dd>
99
+ <dt>offset</dt>
100
+ <dd>0</dd>
101
+ </dl>
102
+
103
+ <dl>
104
+ <dt>id</dt>
105
+ <dd class=monospace>acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37i0</dd>
106
+ <dt>address</dt>
107
+ <dd class=monospace>bc1qx3scwushtwenxtxlnjet4x2w5vg35etdtse5u0</dd>
108
+ <dt>output value</dt>
109
+ <dd>8020</dd>
110
+ <dt>sat</dt>
111
+ <dd><a href=/sat/1883186433806857>1883186433806857</a></dd>
112
+ <dt>preview</dt>
113
+ <dd><a href=/preview/acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37i0>link</a></dd>
114
+ <dt>content</dt>
115
+ <dd><a href=/content/acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37i0>link</a></dd>
116
+ <dt>content length</dt>
117
+ <dd>529 bytes</dd>
118
+ <dt>content type</dt>
119
+ <dd>image/png</dd>
120
+ <dt>timestamp</dt>
121
+ <dd><time>2023-01-31 19:34:47 UTC</time></dd>
122
+ <dt>genesis height</dt>
123
+ <dd><a href=/block/774489>774489</a></dd>
124
+ <dt>genesis fee</dt>
125
+ <dd>5340</dd>
126
+ <dt>genesis transaction</dt>
127
+ <dd><a class=monospace href=/tx/acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37>acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37</a></dd>
128
+ <dt>location</dt>
129
+ <dd class=monospace>6630ff2153985504b180fc16721d559a4cecdc66f4be6acf33509ec2100c0aa5:0:0</dd>
130
+ <dt>output</dt>
131
+ <dd><a class=monospace href=/output/6630ff2153985504b180fc16721d559a4cecdc66f4be6acf33509ec2100c0aa5:0>6630ff2153985504b180fc16721d559a4cecdc66f4be6acf33509ec2100c0aa5:0</a></dd>
132
+ <dt>offset</dt>
133
+ <dd>0</dd>
134
+ </dl>
135
+
136
+
137
+
138
+ <title>Inscription 407</title>
139
+
140
+ id =
141
+ genesis transaction + offset ???
142
+
143
+ genesis transaction: acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37
144
+ id: acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37i0
145
+ address: bc1qx3scwushtwenxtxlnjet4x2w5vg35etdtse5u0
146
+ output value: 8020
147
+ sat: 1883186433806857
148
+ content length: 529 bytes
149
+ content type: image/png
150
+ timestamp: 2023-01-31 19:34:47 UTC
151
+ genesis height: 774489
152
+ genesis fee: 5340
153
+ genesis transaction: acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37>acda637db995df796b35035fd978cc1a947f1e6fd5215968da88b7e38a7e4b37
154
+ location: 6630ff2153985504b180fc16721d559a4cecdc66f4be6acf33509ec2100c0aa5:0:0
155
+ output: 6630ff2153985504b180fc16721d559a4cecdc66f4be6acf33509ec2100c0aa5:0
156
+ offset: 0
157
+
158
+ =end
159
+
160
+ def inscription( id )
161
+ src = "#{@base}/#{@inscription}/#{id}"
162
+ res = get( src )
163
+
164
+ data = _parse_inscription( res.text )
165
+ data
166
+ end
167
+
168
+
169
+ def _parse_inscription( html )
170
+ doc = Nokogiri::HTML( html )
171
+
172
+ items = []
173
+
174
+ title = doc.css( 'head title' )
175
+ items << ['title', title.text]
176
+
177
+
178
+ dls = doc.css( 'body dl' )
179
+ dls[0].css( 'dt,dd' ).each do |el|
180
+ if el.name == 'dt'
181
+ items << [el.text]
182
+ elsif el.name == 'dd'
183
+ items[-1] << el.text
184
+ else
185
+ puts "!! ERROR - unexpected tag; expected dd|dl; got: #{el.name}"
186
+ exit 1
187
+ end
188
+ end
189
+ items
190
+
191
+ ## convert to hash
192
+ ## and check for duplicate
193
+ data = {}
194
+ items.each do |k,v|
195
+ k = k.strip
196
+ v = v.strip
197
+ if data.has_key?( k )
198
+ puts "!! ERROR - duplicate key >#{k}< in:"
199
+ pp items
200
+ exit 1
201
+ end
202
+ data[ k ] = v
203
+ end
204
+ data
205
+ end
206
+
207
+
208
+ def get( src )
209
+ res = Webclient.get( src )
210
+ if res.status.ok?
211
+ res
212
+ else
213
+ puts "!! ERROR - HTTP #{res.status.code} #{res.status.message} - failed web request >#{src}<; sorry"
214
+ exit 1
215
+ end
216
+ end
217
+ end # class Api
218
+
219
+ ##
220
+ ## add convenience alias
221
+ API = Api
222
+
223
+ end ## module Ordinals
224
+
@@ -44,8 +44,8 @@ alias_method :size, :count
44
44
 
45
45
 
46
46
  def each_ordinal( &block )
47
- ordinals.each do |rec| ## pass along hash rec for now - why? why not?
48
- block.call( rec )
47
+ ordinals.each_with_index do |rec, i| ## pass along hash rec for now - why? why not?
48
+ block.call( rec, i )
49
49
  end
50
50
  end
51
51
 
@@ -54,9 +54,9 @@ end
54
54
 
55
55
 
56
56
  def each_image( &block )
57
- each_ordinal do |rec|
58
- id = rec['id']
59
- num = rec['num'].to_i(10)
57
+ each_ordinal do |rec, i|
58
+ id = rec['id']
59
+ num = rec.has_key?('num') ? rec['num'].to_i(10) : i+1
60
60
 
61
61
  path = "./#{@slug}/#{@width}x#{@height}/#{num}.png"
62
62
  img = Image.read( path )
@@ -78,9 +78,9 @@ end
78
78
 
79
79
 
80
80
  def pixelate
81
- each_ordinal do |rec|
82
- id = rec['id']
83
- num = rec['num']
81
+ each_ordinal do |rec, i|
82
+ id = rec['id']
83
+ num = rec.has_key?('num') ? rec['num'].to_i(10) : i+1
84
84
 
85
85
  outpath = "./#{@slug}/#{@width}x#{@height}/#{num}.png"
86
86
  next if File.exist?( outpath )
@@ -113,14 +113,24 @@ def pixelate
113
113
  end
114
114
 
115
115
 
116
+
117
+
116
118
  def make_composite
117
119
  cols, rows = case count
118
120
  when 10 then [5, 2]
121
+ when 12 then [4, 3]
119
122
  when 15 then [5, 3]
123
+ when 20 then [5, 4]
124
+ when 50 then [10, 5]
120
125
  when 69 then [10, 7]
121
- when 99 then [10, 10]
126
+ when 80 then [10, 8]
127
+ when 88 then [10, 9]
128
+ when 98,99 then [10, 10]
122
129
  when 100 then [10, 10]
130
+ when 101 then [11, 10]
123
131
  when 111 then [11, 11]
132
+ when 130 then [10, 13]
133
+ when 512 then [20, 26]
124
134
  else
125
135
  raise ArgumentError, "sorry - unknown composite count #{count} for now"
126
136
  end
@@ -141,51 +151,103 @@ end
141
151
 
142
152
 
143
153
  def convert_images
144
- ## todo: check for gifs too - why? why not?
145
154
  Image.convert( image_dir, from: 'jpg',
146
155
  to: 'png' )
156
+
157
+ Image.convert( image_dir, from: 'gif',
158
+ to: 'png' )
159
+
160
+ Image.convert( image_dir, from: 'webp',
161
+ to: 'png' )
162
+ end
163
+
164
+
165
+
166
+ def fix_images ## todo - find a different names for resaving png images?
167
+ ## "repair" png images
168
+ ## starting w/
169
+ ## - why?
170
+ ##
171
+ ## verify_signature! - ChunkyPNG::SignatureMismatch:
172
+ ## PNG signature not found,
173
+ ## found "RIFF\\xFE\\b\\x00\\x00"
174
+ ## instead of "\\x89PNG\\r\\n\\x1A\\n"
175
+
176
+ Image.convert( image_dir, from: 'png',
177
+ to: 'png' )
178
+ end
179
+
180
+
181
+ def download_meta ## inscription metadata
182
+ each_ordinal do |rec, i|
183
+ id = rec['id']
184
+ num = rec.has_key?('num') ? rec['num'].to_i(10) : i+1
185
+
186
+ chain = Ordinals.config.chain
187
+ path = "../ordinals.cache/#{chain}/#{id}.json"
188
+ next if File.exist?( path )
189
+
190
+ puts "==> downloading #{chain} inscription meta #{num} w/ id #{id}..."
191
+
192
+ data = Ordinals.client.inscription( id )
193
+
194
+ write_json( path, data )
195
+
196
+ sleep( 1.0 ) ## sleep (delay_in_s)
197
+ end
198
+ end
199
+
200
+ def dump_stats
201
+ stats = InscriptionStats.new
202
+
203
+ each_ordinal do |rec, i|
204
+ id = rec['id']
205
+
206
+ chain = Ordinals.config.chain
207
+ path = "../ordinals.cache/#{chain}/#{id}.json"
208
+
209
+ data = read_json( path )
210
+ stats.update( data )
211
+ end
212
+
213
+ pp stats.data
214
+ puts
215
+ puts stats.format
147
216
  end
148
217
 
149
218
 
150
219
  def download_images
151
- each_ordinal do |rec|
152
- id = rec['id']
153
- num = rec['num']
220
+ each_ordinal do |rec, i|
221
+ id = rec['id']
222
+ num = rec.has_key?('num') ? rec['num'].to_i(10) : i+1
154
223
 
155
224
  next if File.exist?( "#{image_dir}/#{num}.png" )
156
225
 
157
226
  puts "==> downloading image ##{num}..."
158
227
 
159
- image_url = "https://ordinals.com/content/#{id}"
160
228
 
161
- res = Webclient.get( image_url )
229
+ content = Ordinals.client.content( id )
162
230
 
163
- if res.status.ok?
164
- content_type = res.content_type
165
- content_length = res.content_length
231
+ puts " content_type: #{content.type}, content_length: #{content.length}"
166
232
 
167
- puts " content_type: #{content_type}, content_length: #{content_length}"
168
-
169
- format = if content_type =~ %r{image/jpeg}i
233
+ format = if content.type =~ %r{image/jpeg}i
170
234
  'jpg'
171
- elsif content_type =~ %r{image/png}i
235
+ elsif content.type =~ %r{image/png}i
172
236
  'png'
173
- elsif content_type =~ %r{image/gif}i
237
+ elsif content.type =~ %r{image/gif}i
174
238
  'gif'
239
+ elsif content.type =~ %r{image/webp}i
240
+ 'webp'
175
241
  else
176
242
  puts "!! ERROR:"
177
- puts " unknown image format content type: >#{content_type}<"
243
+ puts " unknown image format content type: >#{content.type}<"
178
244
  exit 1
179
245
  end
180
246
 
181
247
  ## save image - using b(inary) mode
182
- write_blob( "#{image_dir}/#{num}.#{format}", res.blob )
248
+ write_blob( "#{image_dir}/#{num}.#{format}", content.blob )
183
249
 
184
250
  sleep( 1.0 ) ## sleep (delay_in_s)
185
- else
186
- puts "!! ERROR - failed to download image; sorry - #{res.status.code} #{res.status.message}"
187
- exit 1
188
- end
189
251
  end
190
252
  end
191
253
 
@@ -0,0 +1,142 @@
1
+
2
+
3
+ class InscriptionStats
4
+
5
+ TITLE_RX = /^Inscription[ ]+(?<ord>[0-9]+)$/
6
+ BYTES_RX = /^(?<bytes>[0-9]+)[ ]+bytes$/
7
+
8
+ def initialize
9
+ @stats = {
10
+ count: 0,
11
+ ord: { '<1000' => 0,
12
+ '<10000' => 0,
13
+ '<100000' => 0,
14
+ '<1000000' => 0,
15
+ 'min' => nil,
16
+ 'max' => nil, },
17
+ type: Hash.new(0),
18
+ bytes: { '<100' => 0,
19
+ '<1000' => 0,
20
+ '<10000' => 0,
21
+ '<100000' => 0,
22
+ '<1000000' => 0,
23
+ 'min' => nil,
24
+ 'max' => nil, },
25
+ }
26
+
27
+ @days = Hash.new(0)
28
+ end
29
+
30
+ def size() @stats[:count]; end
31
+
32
+
33
+
34
+ def update( data )
35
+ data = JSON.parse( data) if data.is_a?( String )
36
+
37
+ @stats[ :count ] += 1
38
+
39
+
40
+ ord = nil
41
+ if m=TITLE_RX.match( data['title'] )
42
+ ord = m[:ord].to_i(10)
43
+ else
44
+ puts "!! ERROR - cannot find ord in inscription title; sorry"
45
+ pp data
46
+ exit 1
47
+ end
48
+
49
+ if ord < 1000 then @stats[:ord]['<1000'] += 1
50
+ elsif ord < 10000 then @stats[:ord]['<10000'] += 1
51
+ elsif ord < 100000 then @stats[:ord]['<100000'] += 1
52
+ elsif ord < 1000000 then @stats[:ord]['<1000000'] += 1
53
+ else
54
+ puts "!! ERROR ord out-of-bounds"
55
+ exit 1
56
+ end
57
+
58
+ ## update min/max
59
+ @stats[:ord]['min'] = ord if ord < (@stats[:ord]['min'] || 9999999)
60
+ @stats[:ord]['max'] = ord if ord > (@stats[:ord]['max'] || -1)
61
+
62
+
63
+ bytes = nil
64
+ if m=BYTES_RX.match( data['content length'] )
65
+ bytes = m[:bytes].to_i(10)
66
+ else
67
+ puts "!! ERROR - cannot find bytes in inscription content length; sorry"
68
+ pp data
69
+ exit 1
70
+ end
71
+
72
+ if bytes < 100 then @stats[:bytes]['<100'] += 1
73
+ elsif bytes < 1000 then @stats[:bytes]['<1000'] += 1
74
+ elsif bytes < 10000 then @stats[:bytes]['<10000'] += 1
75
+ elsif bytes < 100000 then @stats[:bytes]['<100000'] += 1
76
+ elsif bytes < 1000000 then @stats[:bytes]['<1000000'] += 1
77
+ else
78
+ puts "!! ERROR bytes (content-length) out-of-bounds"
79
+ exit 1
80
+ end
81
+
82
+ ## update min/max
83
+ @stats[:bytes]['min'] = bytes if bytes < (@stats[:bytes]['min'] || 9999999)
84
+ @stats[:bytes]['max'] = bytes if bytes > (@stats[:bytes]['max'] || -1)
85
+
86
+
87
+ type = data['content type']
88
+
89
+ @stats[:type][ type ] += 1
90
+
91
+ ## "timestamp"=>"2023-02-05 01:45:22 UTC",
92
+ ts = Time.strptime( data['timestamp'], '%Y-%m-%d %H:%M:%S' )
93
+
94
+ @days[ ts.strftime('%Y-%m-%d') ] +=1
95
+ end # method update
96
+
97
+
98
+ def data ## rename to model or such - why? why not?
99
+ ## sort days
100
+ days = @days.sort {|l,r| l[0] <=> r[0] }
101
+ ## add to stats
102
+ @stats[:days] = {}
103
+ days.each {|k,v| @stats[:days][k] = v }
104
+ @stats
105
+ end
106
+
107
+ =begin
108
+ {:count=>101,
109
+ :ord=>{"<1000"=>0, "<10000"=>0, "<100000"=>101, "<1000000"=>0, "min"=>14343, "max"=>42901},
110
+ :type=>{"image/png"=>101},
111
+ :bytes=>{"<100"=>0, "<1000"=>101, "<10000"=>0, "<100000"=>0, "<1000000"=>0, "min"=>274, "max"=>512},
112
+ :days=>{"2023-02-22"=>10, "2023-02-25"=>17, "2023-02-26"=>74}}
113
+ =end
114
+
115
+ def format ## rename to pretty_print or such - why? why not?
116
+ stat = data
117
+
118
+ buf = String.new('')
119
+ buf << "#{stat[:count]} inscription(s)\n"
120
+
121
+ buf << "- from ##{stat[:ord]['min']} to ##{stat[:ord]['max']} (min. to max.)\n"
122
+ buf << " - <1000 => #{stat[:ord]['<1000']}\n" if stat[:ord]["<1000"] > 0
123
+ buf << " - <10000 => #{stat[:ord]['<10000']}\n" if stat[:ord]["<10000"] > 0
124
+ buf << " - <100000 => #{stat[:ord]['<100000']}\n" if stat[:ord]["<100000"] > 0
125
+ buf << " - <1000000 => #{stat[:ord]['<1000000']}\n" if stat[:ord]["<1000000"] > 0
126
+
127
+ buf << "- format(s)\n"
128
+ stat[:type].each do |k,v|
129
+ buf << " - #{k} => #{v}\n"
130
+ end
131
+
132
+ buf << "- day(s)\n"
133
+ stat[:days].each do |k,v|
134
+ buf << " - #{k} => #{v}\n"
135
+ end
136
+
137
+ buf
138
+ end
139
+
140
+ end ## class InscriptionStats
141
+
142
+
@@ -0,0 +1,121 @@
1
+ module Ordinals
2
+ class Tool
3
+
4
+ def self.main( args=ARGV )
5
+ puts "==> welcome to ordinals/ordbase tool with args:"
6
+ pp args
7
+
8
+ options = {
9
+ }
10
+
11
+ parser = OptionParser.new do |opts|
12
+ opts.on( "--doge", "--dogecoin", "Use Dogecoin / DOGE") do
13
+ ## switch to doge
14
+ Ordinals.config.chain = :doge
15
+ end
16
+ opts.on( "--ltc", "--litecoin", "Use Litecoin / LTC") do
17
+ ## switch to ltc
18
+ Ordinals.config.chain = :ltc
19
+ end
20
+ opts.on( "--btc", "--bitcoin", "Use Bitcoin / BTC") do
21
+ ## switch to btc (default)
22
+ Ordinals.config.chain = :btc
23
+ end
24
+
25
+ opts.on("-h", "--help", "Prints this help") do
26
+ puts opts
27
+ exit
28
+ end
29
+ end
30
+
31
+ parser.parse!( args )
32
+ puts "options:"
33
+ pp options
34
+
35
+ puts "args:"
36
+ pp args
37
+
38
+ if args.size < 1
39
+ puts "!! ERROR - no collection found - use <collection> <command>..."
40
+ puts ""
41
+ exit
42
+ end
43
+
44
+ slug = args[0]
45
+ command = args[1] || 'image'
46
+
47
+ if ['m', 'meta'].include?( command )
48
+ do_download_meta( slug )
49
+ elsif ['stat', 'stats'].include?( command )
50
+ do_dump_stats( slug )
51
+ elsif ['i','img', 'image'].include?( command )
52
+ do_download_images( slug )
53
+ elsif ['conv','convert'].include?( command )
54
+ do_convert_images( slug )
55
+ elsif ['fix'].include?( command )
56
+ do_fix_images( slug )
57
+ elsif ['px','pix', 'pixelate' ].include?( command )
58
+ do_pixelate( slug )
59
+ elsif ['comp','composite' ].include?( command )
60
+ do_make_composite( slug )
61
+ else
62
+ puts "!! ERROR - unknown command >#{command}<, sorry"
63
+ end
64
+
65
+ puts "bye"
66
+ end
67
+
68
+
69
+ def self.do_dump_stats( slug )
70
+ puts "==> dump inscription stats for collection >#{slug}<..."
71
+
72
+ col = Collection.new( slug )
73
+ col.dump_stats
74
+ end
75
+
76
+ def self.do_download_meta( slug )
77
+ puts "==> download meta for collection >#{slug}<..."
78
+
79
+ col = Collection.new( slug )
80
+ col.download_meta
81
+ end
82
+
83
+ def self.do_download_images( slug )
84
+ puts "==> download images for collection >#{slug}<..."
85
+
86
+ col = Collection.new( slug )
87
+ col.download_images
88
+ end
89
+
90
+
91
+ def self.do_convert_images( slug )
92
+ puts "==> convert images for collection >#{slug}<..."
93
+
94
+ col = Collection.new( slug )
95
+ col.convert_images
96
+ end
97
+
98
+ def self.do_fix_images( slug )
99
+ puts "==> fix images for collection >#{slug}<..."
100
+
101
+ col = Collection.new( slug )
102
+ col.fix_images
103
+ end
104
+
105
+
106
+ def self.do_pixelate( slug )
107
+ puts "==> downsample / pixelate images for collection >#{slug}<..."
108
+
109
+ col = Collection.new( slug )
110
+ col.pixelate
111
+ end
112
+
113
+ def self.do_make_composite( slug )
114
+ puts "==> make composite for collection >#{slug}<..."
115
+
116
+ col = Collection.new( slug )
117
+ col.make_composite
118
+ end
119
+
120
+ end # class Tool
121
+ end # module Ordinals
data/lib/ordinals.rb CHANGED
@@ -5,91 +5,88 @@ require 'pixelart'
5
5
  require 'optparse'
6
6
 
7
7
 
8
-
9
- ## our own code
10
- require_relative 'ordinals/collection'
11
-
8
+ ## add nokogiri for api (html parsing)
9
+ require 'nokogiri'
12
10
 
13
11
 
14
- module Ordinals
15
- class Tool
16
-
17
- def self.main( args=ARGV )
18
- puts "==> welcome to ordinals/ordbase tool with args:"
19
- pp args
20
12
 
21
- options = {
22
- }
23
13
 
24
- parser = OptionParser.new do |opts|
25
14
 
26
- opts.on("-h", "--help", "Prints this help") do
27
- puts opts
28
- exit
29
- end
15
+ module Ordinals
16
+ class Configuration
17
+
18
+ #######################
19
+ ## accessors
20
+ def chain=(value)
21
+ if value.is_a?( String ) || value.is_a?( Symbol )
22
+ case value.downcase.to_s
23
+ when 'btc', 'bitcoin', 'bitcon'
24
+ @chain = 'btc'
25
+ @client = Ordinals::Api.bitcoin
26
+ when 'ltc', 'litecoin', 'litecon'
27
+ @chain = 'ltc'
28
+ @client = Ordinals::Api.litecoin
29
+ when 'doge', 'dogecoin', 'dogecon'
30
+ @chain = 'doge'
31
+ @client = Ordinals::Api.dogecoin
32
+ else
33
+ raise ArgumentError, "unknown chain - expected btc | ltc | doge; got #{value}"
34
+ end
35
+ else
36
+ raise ArgumentError, "only string or symbol supported for now; sorry - got: #{value.inspect} : #{value.class.name}"
37
+ end
30
38
  end
31
39
 
32
- parser.parse!( args )
33
- puts "options:"
34
- pp options
35
-
36
- puts "args:"
37
- pp args
38
-
39
- if args.size < 1
40
- puts "!! ERROR - no collection found - use <collection> <command>..."
41
- puts ""
42
- exit
40
+ def chain
41
+ ## note - default to btc/bitcon if not set
42
+ self.chain = 'btc' unless defined?( @chain )
43
+ @chain
43
44
  end
44
45
 
45
- slug = args[0]
46
- command = args[1] || 'image'
47
-
48
- if ['i','img', 'image'].include?( command )
49
- do_download_images( slug )
50
- elsif ['conv','convert'].include?( command )
51
- do_convert_images( slug )
52
- elsif ['px','pixelate' ].include?( command )
53
- do_pixelate( slug )
54
- elsif ['comp','composite' ].include?( command )
55
- do_make_composite( slug )
56
- else
57
- puts "!! ERROR - unknown command >#{command}<, sorry"
46
+ ## note: read-only for now - why? why not?
47
+ def client
48
+ ## note - default to btc/bitcon if not set
49
+ self.chain = 'btc' unless defined?( @client )
50
+ @client
58
51
  end
52
+ end # class Configuration
59
53
 
60
- puts "bye"
61
- end
62
54
 
55
+ ## lets you use
56
+ ## Ordinals.configure do |config|
57
+ ## config.chain = :btc
58
+ ## end
59
+ def self.configure() yield( config ); end
60
+ def self.config() @config ||= Configuration.new; end
63
61
 
62
+ ## add some convenience shortcut helpers (no config. required) - why? why not?
63
+ def self.client() config.client; end
64
+ def self.chain() config.chain; end
64
65
 
65
- def self.do_download_images( slug )
66
- puts "==> download images for collection >#{slug}<..."
66
+ def self.btc?() config.chain == 'btc'; end
67
+ def self.ltc?() config.chain == 'ltc'; end
68
+ def self.doge?() config.chain == 'doge'; end
69
+ class << self
70
+ alias_method :bitcoin?, :btc?
71
+ alias_method :bitcon?, :btc?
67
72
 
68
- col = Collection.new( slug )
69
- col.download_images
73
+ alias_method :litecoin?, :ltc?
74
+ alias_method :litecon?, :ltc?
75
+
76
+ alias_method :dogecoin?, :doge?
77
+ alias_method :dogecon?, :doge?
70
78
  end
79
+ end # module Ordinals
71
80
 
72
81
 
73
- def self.do_convert_images( slug )
74
- puts "==> convert images for collection >#{slug}<..."
75
82
 
76
- col = Collection.new( slug )
77
- col.convert_images
78
- end
79
83
 
80
- def self.do_pixelate( slug )
81
- puts "==> downsample / pixelate images for collection >#{slug}<..."
84
+ ## our own code
85
+ require_relative 'ordinals/api'
86
+ require_relative 'ordinals/collection'
87
+ require_relative 'ordinals/stats'
82
88
 
83
- col = Collection.new( slug )
84
- col.pixelate
85
- end
89
+ require_relative 'ordinals/tool'
86
90
 
87
- def self.do_make_composite( slug )
88
- puts "==> make composite for collection >#{slug}<..."
89
91
 
90
- col = Collection.new( slug )
91
- col.make_composite
92
- end
93
92
 
94
- end # class Tool
95
- end # module Ordinals
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ordinals
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-13 00:00:00.000000000 Z
11
+ date: 2023-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocos
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rdoc
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -73,7 +87,7 @@ dependencies:
73
87
  - !ruby/object:Gem::Version
74
88
  version: '3.23'
75
89
  description: ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery
76
- & helpers for Bitcoin & co.
90
+ & helpers for Bitcoin, Litecoin, Dogecoin & co.
77
91
  email: wwwmake@googlegroups.com
78
92
  executables:
79
93
  - ordbase
@@ -89,7 +103,10 @@ files:
89
103
  - Rakefile
90
104
  - bin/ordbase
91
105
  - lib/ordinals.rb
106
+ - lib/ordinals/api.rb
92
107
  - lib/ordinals/collection.rb
108
+ - lib/ordinals/stats.rb
109
+ - lib/ordinals/tool.rb
93
110
  homepage: https://github.com/pixelartexchange/ordinals.sandbox
94
111
  licenses:
95
112
  - Public Domain
@@ -115,5 +132,5 @@ rubygems_version: 3.3.7
115
132
  signing_key:
116
133
  specification_version: 4
117
134
  summary: ordinals gem - "right-clicker" (off-chain) ordinals (pixel art) machinery
118
- & helpers for Bitcoin & co.
135
+ & helpers for Bitcoin, Litecoin, Dogecoin & co.
119
136
  test_files: []