ordinals 0.1.0 → 1.0.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.
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: []