scribelite 0.2.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: '0608ffa940a6f65b4432a6e8e494ec2213e20a6fb661e15eafb9f0408f1175be'
4
- data.tar.gz: 5b019fbe73890c1b20a5c4106a617e2c573431e805b47825fb056f7bd50449aa
3
+ metadata.gz: d2a98f4c256e13196f739fb4f538a954f50f51b85ed4bd3fedb3041e66dd3580
4
+ data.tar.gz: 5e9009cef8b739e5e02d630f0e751528a93665d81ba08cecb6ac0e50f8739875
5
5
  SHA512:
6
- metadata.gz: 3a363643f683a32c1ccbd04448d790b6582f5ccc7d8d3176ab65506ff92d826a5b54e37b41add46bb8c8b577a8f76aa9d8ee7800a7d946aa7cf5ce9802fb283b
7
- data.tar.gz: 491db0258ff4a4dc27974f9e3e8e8147a9437690dfdd2c30bf8d81b8710aee0f7ee4a119cd3657b6d48f1cc3399570e4787ef9fbdc87d17ec4f4115acb79d1e7
6
+ metadata.gz: 2b920e7a86fe0c47a4b75870c2ae1d27805210b82511bab685cca55af763a93f4f7c9b3124d449eb350b2f22953a920119749b389f6cb357ce5b5750f5282633
7
+ data.tar.gz: 69177a203454d697914a49bf9cfc1b7bc94e00cb149b4a012d01e808433fe7ab0c6d6b3748de8222860d9860a265d2e7d82dc7dcb7c30f246001646aa0a39ec0
data/CHANGELOG.md CHANGED
@@ -1,4 +1,5 @@
1
- ### 0.2.0
1
+ ### 1.0.0
2
+ ### 0.2.1
2
3
  ### 0.0.1 / 2023-11-21
3
4
 
4
5
  * Everything is new. First release
data/Manifest.txt CHANGED
@@ -3,6 +3,7 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/scribelite.rb
6
+ lib/scribelite/importer.rb
6
7
  lib/scribelite/models/forward.rb
7
8
  lib/scribelite/models/scribe.rb
8
9
  lib/scribelite/models/tx.rb
data/README.md CHANGED
@@ -4,8 +4,8 @@ scribelite - inscription / inscribe (ethscription calldata) database for ethereu
4
4
 
5
5
 
6
6
 
7
- * home :: [github.com/s6ruby/rubidity](https://github.com/s6ruby/rubidity)
8
- * bugs :: [github.com/s6ruby/rubidity/issues](https://github.com/s6ruby/rubidity/issues)
7
+ * home :: [github.com/0xCompute/ethscribe](https://github.com/0xCompute/ethscribe)
8
+ * bugs :: [github.com/0xCompute/ethscribe/issues](https://github.com/0xCompute/ethscribe/issues)
9
9
  * gem :: [rubygems.org/gems/scribelite](https://rubygems.org/gems/scribelite)
10
10
  * rdoc :: [rubydoc.info/gems/scribelite](http://rubydoc.info/gems/scribelite)
11
11
 
@@ -25,24 +25,23 @@ The work-in-progess database schema looks like:
25
25
  ActiveRecord::Schema.define do
26
26
 
27
27
  create_table :scribes, :id => :string do |t|
28
- t.integer :num, null: false, index: { unique: true, name: 'scribe_nums' }
29
- t.integer :bytes
30
- t.string :content_type
28
+ t.integer :num, null: false, index: { unique: true, name: 'scribe_nums' }
29
+ t.integer :bytes
30
+ t.string :content_type
31
31
  ## add allow duplicate opt-in protocol flag e.g. esip6
32
32
  t.boolean :duplicate ## allows duplicates flag
33
33
  t.boolean :flagged, null: false, default: false ## censored flag / removed on request
34
- ## move sha to tx - why? why not?
35
34
  t.string :sha, null: false ## sha hash as hexstring (but no leading 0)
36
35
  end
37
36
 
38
37
  create_table :txs, :id => :string do |t|
39
38
  t.binary :data # , null: false
40
- t.datetime :date, null: false
39
+ t.datetime :date, null: false
41
40
  t.integer :block, null: false
42
41
  t.integer :idx, null: false ## transaction index (number)
43
42
 
44
- t.string :from, null: false
45
- t.string :to, null: false
43
+ t.string :from, null: false
44
+ t.string :to, null: false
46
45
 
47
46
  t.integer :fee
48
47
  t.integer :value
@@ -62,6 +61,20 @@ ScribeDb.connect( adapter: 'sqlite3',
62
61
  ScribeDb.create_all ## build schema
63
62
  ```
64
63
 
64
+ and lets import the first hundred (page size is 25) ethscriptions on mainnet (via the ethscriptions.com api):
65
+
66
+
67
+ ``` ruby
68
+ require 'scribelite'
69
+
70
+ ScribeDb.open( './scribe.db' )
71
+
72
+ (1..4).each do |page|
73
+ ScribeDb.import_ethscriptions( page: page )
74
+ end
75
+ ```
76
+
77
+
65
78
 
66
79
  and to query use it like:
67
80
 
@@ -93,9 +106,28 @@ Scribe.order( :num ).limit(limit).each do |scribe|
93
106
  print "#{scribe.num} / #{scribe.content_type} - #{scribe.tx.date} @ #{scribe.tx.block}"
94
107
  print "\n"
95
108
  end
96
- ```
97
109
 
98
110
 
111
+ ## Let's query for all inscriptions grouped by date (day)
112
+ ## and dump the results:
113
+ pp Scribe.counts_by_day
114
+ pp Scribe.counts_by_year
115
+ pp Scribe.counts_by_month
116
+ pp Scribe.counts_by_hour
117
+
118
+
119
+ ## Let's query for all content types and group by count (descending)
120
+ ## and dump the results:
121
+ pp Scribe.counts_by_content_type
122
+
123
+ pp Scribe.counts_by_block
124
+ pp Scribe.counts_by_block_with_timestamp
125
+
126
+ pp Scribe.counts_by_address # from (creator/minter) address
127
+
128
+ # ...
129
+ ```
130
+
99
131
  To be continued...
100
132
 
101
133
 
@@ -110,7 +142,9 @@ at the ruby code commons (rubycocos) org.
110
142
 
111
143
  ## Questions? Comments?
112
144
 
113
- Join us in the [Rubidity (community) discord (chat server)](https://discord.gg/3JRnDUap6y). Yes you can.
145
+ Join us in the [0xCompute discord (chat server)](https://discord.gg/3JRnDUap6y)
146
+ (or in the more general Ethscription discord).
147
+ Yes you can.
114
148
  Your questions and commentary welcome.
115
149
 
116
150
  Or post them over at the [Help & Support](https://github.com/geraldb/help) page. Thanks.
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ Hoe.spec 'scribelite' do
8
8
  self.summary = "scribelite gem - inscription / inscribe (ethscription calldata) database for ethereum & co; let's you query via sql and more"
9
9
  self.description = summary
10
10
 
11
- self.urls = { home: 'https://github.com/s6ruby/rubidity' }
11
+ self.urls = { home: 'https://github.com/0xCompute/ethscribe' }
12
12
 
13
13
  self.author = 'Gerald Bauer'
14
14
  self.email = 'gerald.bauer@gmail.com'
@@ -0,0 +1,155 @@
1
+
2
+
3
+ module ScribeDb
4
+
5
+
6
+
7
+ ## note: by default - sort asc(ending) - oldest first (0,1,2,3, .etc.)
8
+ def self.import_ethscriptions( page: 1,
9
+ per_page: 50,
10
+ sort_order: 'asc' )
11
+ net = Ethscribe.config.client
12
+ recs = net.ethscriptions( page: page,
13
+ per_page: per_page,
14
+ sort_order: sort_order )
15
+
16
+ puts " #{recs.size} record(s)"
17
+
18
+ recs.each_with_index do |rec,i|
19
+ puts "==> page #{page}/#{i+1} - #{rec['transaction_hash']}..."
20
+ _import_ethscription( rec )
21
+ end
22
+
23
+ recs.size ## return number of records fetched for now
24
+ end # method import_ethscriptions
25
+
26
+
27
+ def self.sync_facet_txs
28
+ net = Ethscribe.config.client
29
+
30
+ ## add block argument - why? why not?
31
+ ## get count by block e.g.
32
+ ## our_count = Scribe.joins(:tx).where( 'block < ?', new_block_number).count
33
+
34
+
35
+ count = Scribe.count
36
+
37
+ block = if count > 0
38
+ last_scribe = Scribe.where( num: Scribe.maximum(:num) ).first
39
+ last_scribe.tx.block + 1
40
+ else
41
+ 0
42
+ end
43
+
44
+ loop do
45
+ res = net.newer_facet_txs( block, max: 2500, count: count )
46
+
47
+ break if res['blocks'].size == 0
48
+
49
+ first_block = res['blocks'][0]['block_number']
50
+ last_block = res['blocks'][-1]['block_number']
51
+ puts "==> #{res['blocks'].size} block(s) - #{first_block} to #{last_block}..."
52
+ puts " total_future_ethscriptions: #{res['total_future_ethscriptions']}"
53
+
54
+ batch_count = 0
55
+ res['blocks'].each do |block_rec|
56
+ block_rec['ethscriptions'].each do |rec|
57
+ _import_ethscription( rec )
58
+ count += 1
59
+ batch_count += 1
60
+ end
61
+ end
62
+ puts " #{batch_count} record(s) added"
63
+
64
+ block = last_block + 1
65
+
66
+ break if res['total_future_ethscriptions'] == 0
67
+ end
68
+ end # method sync_facet_txs
69
+ class << self
70
+ alias_method :sync_facet_txns, :sync_facet_txs
71
+ end
72
+
73
+
74
+
75
+ def self._import_ethscription( rec )
76
+ txid = rec['transaction_hash']
77
+ block = rec['block_number']
78
+ idx = rec['transaction_index']
79
+
80
+ from = rec['creator']
81
+ to = rec['initial_owner']
82
+
83
+ ## todo - double check if daylight saving time (dst) breaks timestamp == utc identity/conversion?
84
+ ## 2001-02-03T04:05:06+07:00
85
+ ## 2016-05-29T22:28:15.000Z
86
+ ## check if %z can handle .000Z ??
87
+ ## DateTime.strptime( '2016-05-29T22:28:15.000Z', '%Y-%m-%dT%H:%M:%S%z' )
88
+ ## pp rec['creation_timestamp']
89
+ date = DateTime.strptime( rec['creation_timestamp'], '%Y-%m-%dT%H:%M:%S.000Z' )
90
+
91
+
92
+ num = rec['ethscription_number']
93
+ content_type = rec['mimetype']
94
+ data = rec['content_uri']
95
+ sha = rec['sha']
96
+
97
+ duplicate = rec['esip6']
98
+ flagged = rec['image_removed_by_request_of_rights_holder']
99
+
100
+ ### check - if flagged
101
+ ## content_type always set to text/plain
102
+ ## and data to data:, ???
103
+ ## (re)set to nil/null - why? why not?
104
+ if flagged
105
+ data = nil
106
+ content_type = nil
107
+ end
108
+
109
+ tx_attribs = {
110
+ id: txid,
111
+ block: block,
112
+ idx: idx,
113
+ date: date,
114
+ from: from,
115
+ to: to,
116
+ data: data, ## note: for now saved as utf8 string (not hex!!!!)
117
+ }
118
+
119
+ scribe_attribs = {
120
+ id: txid,
121
+ num: num,
122
+ content_type: content_type,
123
+ duplicate: duplicate,
124
+ flagged: flagged,
125
+ sha: sha,
126
+ bytes: data ? data.length : nil ## auto-add/calc bytes (content length)
127
+ }
128
+
129
+ if num.nil?
130
+ puts "!! skipping unconfirmed / unassigned inscribe - no. num"
131
+ return
132
+ end
133
+
134
+
135
+ scribe = Scribe.find_by( id: txid )
136
+ if scribe
137
+ ## skip for now - found id db
138
+ else
139
+ # puts "scribe_attribs:"
140
+ # pp scribe_attribs
141
+ Scribe.create!( **scribe_attribs )
142
+ end
143
+
144
+ tx = Tx.find_by( id: txid )
145
+ if tx
146
+ ## skip for now - found id db
147
+ else
148
+ # puts "tx_attribs:"
149
+ # pp tx_attribs
150
+ Tx.create!( **tx_attribs )
151
+ end
152
+ end
153
+
154
+ end # module ScribeDb
155
+
@@ -17,11 +17,18 @@ module ScribeDb
17
17
  ### scope like helpers
18
18
  def self.png() where( content_type: 'image/png' ); end
19
19
  def self.gif() where( content_type: 'image/gif' ); end
20
- def self.jpg() where( content_type: 'image/jpeg' ); end
20
+ def self.jpg() where( content_type: ['image/jpeg',
21
+ 'image/jpg'] ); end
21
22
  def self.webp() where( content_type: 'image/webp' ); end
22
23
  def self.svg() where( content_type: 'image/svg+xml' ); end
23
24
  def self.avif() where( content_type: 'image/avif' ); end
24
25
 
26
+ def self.pdf() where( content_type: 'application/pdf' ); end
27
+
28
+ def self.facet() where( content_type: 'application/vnd.facet.tx+json' ); end
29
+
30
+
31
+
25
32
  class << self
26
33
  alias_method :jpeg, :jpg
27
34
  end
@@ -31,6 +38,7 @@ module ScribeDb
31
38
  where( content_type: [
32
39
  'image/png',
33
40
  'image/jpeg',
41
+ 'image/jpg',
34
42
  'image/gif',
35
43
  'image/webp',
36
44
  'image/svg+xml',
@@ -98,90 +106,63 @@ module ScribeDb
98
106
  .order( Arel.sql( 'COUNT(*) DESC, content_type')).count
99
107
  end
100
108
 
101
- class << self
102
- alias_method :biggest, :largest
103
- alias_method :counts_by_content_type, :content_type_counts
109
+
110
+ def self.block_counts
111
+ joins(:tx).group( 'block' )
112
+ .order( 'block').count
113
+ end
114
+
115
+ def self.block_with_timestamp_counts
116
+ joins(:tx).group( Arel.sql( "block || ' @ ' || date" ))
117
+ .order( 'block' ).count
104
118
  end
105
119
 
106
- ###
107
- # instance methods
108
- def extname
109
- ## map mime type to file extname
110
- ## see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
111
- ## for real-world usage, see https://dune.com/dgtl_assets/bitcoin-ordinals-analysis
112
- ## https://github.com/casey/ord/blob/master/src/media.rs
113
-
114
- if content_type.start_with?( 'text/plain' )
115
- '.txt'
116
- elsif content_type.start_with?( 'text/markdown' )
117
- '.md'
118
- elsif content_type.start_with?( 'text/html' )
119
- '.html'
120
- elsif content_type.start_with?( 'text/javascript' ) ||
121
- content_type.start_with?( 'application/javascript' )
122
- ## note: application/javascript is considered bad practice/legacy
123
- '.js'
124
- elsif content_type.start_with?( 'image/png' )
125
- ## Portable Network Graphics (PNG)
126
- '.png'
127
- elsif content_type.start_with?( 'image/jpeg' )
128
- ## Joint Photographic Expert Group image (JPEG)
129
- '.jpg' ## use jpeg - why? why not?
130
- elsif content_type.start_with?( 'image/webp' )
131
- ## Web Picture format (WEBP)
132
- '.webp' ## note: no three-letter extension available
133
- elsif content_type.start_with?( 'image/svg' )
134
- ## Scalable Vector Graphics (SVG)
135
- '.svg'
136
- elsif content_type.start_with?( 'image/gif' )
137
- ## Graphics Interchange Format (GIF)
138
- '.gif'
139
- elsif content_type.start_with?( 'image/avif' )
140
- ## AV1 Image File Format (AVIF)
141
- '.avif'
142
- elsif content_type.start_with?( 'application/epub' )
143
- '.epub'
144
- elsif content_type.start_with?( 'application/pdf' )
145
- '.pdf'
146
- elsif content_type.start_with?( 'application/json' )
147
- '.json'
148
- elsif content_type.start_with?( 'application/pgp-signature' )
149
- '.sig'
150
- elsif content_type.start_with?( 'audio/mpeg' )
151
- '.mp3'
152
- elsif content_type.start_with?( 'audio/midi' )
153
- '.midi'
154
- elsif content_type.start_with?( 'video/mp4' )
155
- '.mp4'
156
- elsif content_type.start_with?( 'video/webm' )
157
- '.wepm'
158
- elsif content_type.start_with?( 'audio/mod' )
159
- ## is typo? possible? only one inscription in 20m?
160
- '.mod' ## check/todo/fix if is .wav??
161
- else
162
- puts "!! ERROR - no file extension configured for content type >#{content_type}<; sorry:"
163
- pp self
164
- exit 1
120
+
121
+ def self.date_counts
122
+ ## note: strftime is SQLite specific/only!!!
123
+ joins(:tx).group( Arel.sql("strftime('%Y-%m-%d', date)"))
124
+ .order( Arel.sql("strftime('%Y-%m-%d', date)")).count
165
125
  end
166
- end
167
-
168
- =begin
169
- def export_path ## default export path
170
- numstr = "%08d" % num ### e.g. 00000001
171
- "./tmp/#{numstr}#{extname}"
172
- end
173
- def export( path=export_path )
174
- if blob
175
- write_blob( path, blob.content )
176
- else
177
- ## todo/fix: raise exception - no content
178
- puts "!! ERROR - inscribe has no content (blob); sorry:"
179
- pp self
180
- exit 1
126
+
127
+ def self.month_counts
128
+ ## note: strftime is SQLite specific/only!!!
129
+ joins(:tx).group( Arel.sql("strftime('%Y-%m', date)"))
130
+ .order( Arel.sql("strftime('%Y-%m', date)")).count
181
131
  end
182
- end
183
- =end
184
132
 
133
+ def self.year_counts
134
+ ## note: strftime is SQLite specific/only!!!
135
+ joins(:tx).group( Arel.sql("strftime('%Y', date)"))
136
+ .order( Arel.sql("strftime('%Y', date)")).count
137
+ end
138
+
139
+ def self.hour_counts
140
+ ## note: strftime is SQLite specific/only!!!
141
+ joins(:tx).group( Arel.sql("strftime('%Y-%m-%d %Hh', date)"))
142
+ .order( Arel.sql("strftime('%Y-%m-%d %Hh', date)")).count
143
+ end
144
+
145
+
146
+ def self.from_counts
147
+ ## note: from is sql keyword!!!
148
+ ## wrap in [] for sqlite - check if works for others!!!
149
+ joins(:tx).group( '[from]' )
150
+ .order( Arel.sql( 'COUNT(*) DESC')).count
151
+ end
152
+
153
+
154
+ class << self
155
+ alias_method :biggest, :largest
156
+ alias_method :counts_by_content_type, :content_type_counts
157
+ alias_method :counts_by_date, :date_counts
158
+ alias_method :counts_by_day, :date_counts
159
+ alias_method :counts_by_month, :month_counts
160
+ alias_method :counts_by_year, :year_counts
161
+ alias_method :counts_by_hour, :hour_counts
162
+ alias_method :counts_by_block, :block_counts
163
+ alias_method :counts_by_block_with_timestamp, :block_with_timestamp_counts
164
+ alias_method :counts_by_address, :from_counts
165
+ end
185
166
  end # class Scribe
186
167
 
187
168
  end # module Model
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Scribelite
3
- MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
- MINOR = 2
3
+ MAJOR = 1 ## todo: namespace inside version or something - why? why not??
4
+ MINOR = 0
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
data/lib/scribelite.rb CHANGED
@@ -34,8 +34,6 @@ require_relative 'scribelite/models/tx'
34
34
 
35
35
  require_relative 'scribelite/schema'
36
36
 
37
- # require_relative 'cache'
38
- # require_relative 'importer' ## note: require (soft dep) ordinals gems!!!
39
37
 
40
38
 
41
39
 
@@ -134,6 +132,11 @@ end # module ScribeDb
134
132
 
135
133
 
136
134
 
135
+ # add ethscriptions / ethscripe importer - why? why not?
136
+ require_relative 'scribelite/importer'
137
+
138
+
139
+
137
140
 
138
141
 
139
142
  ## add convenience helpers
@@ -141,6 +144,13 @@ Scribe = ScribeDb::Model::Scribe
141
144
  Tx = ScribeDb::Model::Tx
142
145
 
143
146
 
147
+
148
+ require 'active_support/number_helper'
149
+ include ActiveSupport::NumberHelper ## e.g. number_to_human_size
150
+
151
+
152
+
153
+
144
154
  # say hello
145
155
  puts Scribelite.banner ## if defined?($RUBYCOCOS_DEBUG) && $RUBCOCOS_DEBUG
146
156
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scribelite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.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-11-25 00:00:00.000000000 Z
11
+ date: 2024-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ethscribe
@@ -162,14 +162,14 @@ dependencies:
162
162
  requirements:
163
163
  - - "~>"
164
164
  - !ruby/object:Gem::Version
165
- version: '4.0'
165
+ version: '4.1'
166
166
  type: :development
167
167
  prerelease: false
168
168
  version_requirements: !ruby/object:Gem::Requirement
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '4.0'
172
+ version: '4.1'
173
173
  description: scribelite gem - inscription / inscribe (ethscription calldata) database
174
174
  for ethereum & co; let's you query via sql and more
175
175
  email: gerald.bauer@gmail.com
@@ -185,12 +185,13 @@ files:
185
185
  - README.md
186
186
  - Rakefile
187
187
  - lib/scribelite.rb
188
+ - lib/scribelite/importer.rb
188
189
  - lib/scribelite/models/forward.rb
189
190
  - lib/scribelite/models/scribe.rb
190
191
  - lib/scribelite/models/tx.rb
191
192
  - lib/scribelite/schema.rb
192
193
  - lib/scribelite/version.rb
193
- homepage: https://github.com/s6ruby/rubidity
194
+ homepage: https://github.com/0xCompute/ethscribe
194
195
  licenses:
195
196
  - Public Domain
196
197
  metadata: {}