scribelite 0.2.0 → 0.2.1

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: 8423f47a4a80b7687f04a31e0457500faa3b52e48a40e239377f0877271aface
4
+ data.tar.gz: 86413fdd0e42317307cb97f0989fca3f8b82736539d4333687ff6c9f158f5915
5
5
  SHA512:
6
- metadata.gz: 3a363643f683a32c1ccbd04448d790b6582f5ccc7d8d3176ab65506ff92d826a5b54e37b41add46bb8c8b577a8f76aa9d8ee7800a7d946aa7cf5ce9802fb283b
7
- data.tar.gz: 491db0258ff4a4dc27974f9e3e8e8147a9437690dfdd2c30bf8d81b8710aee0f7ee4a119cd3657b6d48f1cc3399570e4787ef9fbdc87d17ec4f4115acb79d1e7
6
+ metadata.gz: bfb49887358be1a89b68c855cdce8a0c2dbafa3439f76a0ef0c8dea47ceb00316d73d82577ba01f8112a839d7dc468e312ebcc9c4e0882f7f50fd3c5b560ae9e
7
+ data.tar.gz: d77a2a72689f2e49292edbcb4c02f74b96b1135a2ccf32a33c20c6e3bd79c12288143f8237ef4e06305d9bfa95c9fd5f7934ca36d1f521284d5269c2a8460cc4
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 0.2.0
1
+ ### 0.2.1
2
2
  ### 0.0.1 / 2023-11-21
3
3
 
4
4
  * 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
@@ -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
 
@@ -0,0 +1,91 @@
1
+
2
+
3
+ module ScribeDb
4
+
5
+ ## note: by default - sort asc(ending) - oldest first (0,1,2,3, .etc.)
6
+ def self.import_ethscriptions( page: 1, sort_order: 'asc' )
7
+ net = Ethscribe::Api.mainnet
8
+ recs = net.ethscriptions( page: page, sort_order: sort_order )
9
+
10
+ recs.each_with_index do |rec,i|
11
+ puts "==> page #{page}/#{i+1} - #{rec['transaction_hash']}..."
12
+
13
+ txid = rec['transaction_hash']
14
+ block = rec['block_number']
15
+ idx = rec['transaction_index']
16
+
17
+ from = rec['creator']
18
+ to = rec['initial_owner']
19
+
20
+ ## todo - double check if daylight saving time (dst) breaks timestamp == utc identity/conversion?
21
+ ## 2001-02-03T04:05:06+07:00
22
+ ## 2016-05-29T22:28:15.000Z
23
+ ## check if %z can handle .000Z ??
24
+ ## DateTime.strptime( '2016-05-29T22:28:15.000Z', '%Y-%m-%dT%H:%M:%S%z' )
25
+ ## pp rec['creation_timestamp']
26
+ date = DateTime.strptime( rec['creation_timestamp'], '%Y-%m-%dT%H:%M:%S.000Z' )
27
+
28
+
29
+ num = rec['ethscription_number']
30
+ content_type = rec['mimetype']
31
+ data = rec['content_uri']
32
+ sha = rec['sha']
33
+
34
+ duplicate = rec['esip6']
35
+ flagged = rec['image_removed_by_request_of_rights_holder']
36
+
37
+ ### check - if flagged
38
+ ## content_type always set to text/plain
39
+ ## and data to data:, ???
40
+ ## (re)set to nil/null - why? why not?
41
+ if flagged
42
+ data = nil
43
+ content_type = nil
44
+ end
45
+
46
+ tx_attribs = {
47
+ id: txid,
48
+ block: block,
49
+ idx: idx,
50
+ date: date,
51
+ from: from,
52
+ to: to,
53
+ data: data, ## note: for now saved as utf8 string (not hex!!!!)
54
+ }
55
+
56
+ scribe_attribs = {
57
+ id: txid,
58
+ num: num,
59
+ content_type: content_type,
60
+ duplicate: duplicate,
61
+ flagged: flagged,
62
+ sha: sha,
63
+ bytes: data ? data.length : nil ## auto-add/calc bytes (content length)
64
+ }
65
+
66
+
67
+
68
+
69
+ scribe = Scribe.find_by( id: txid )
70
+ if scribe
71
+ ## skip for now - found id db
72
+ else
73
+ puts "scribe_attribs:"
74
+ pp scribe_attribs
75
+ Scribe.create!( **scribe_attribs )
76
+ end
77
+
78
+ tx = Tx.find_by( id: txid )
79
+ if tx
80
+ ## skip for now - found id db
81
+ else
82
+ puts "tx_attribs:"
83
+ pp tx_attribs
84
+ Tx.create!( **tx_attribs )
85
+ end
86
+ end
87
+
88
+ end # method import_ethscriptions
89
+
90
+ end # module ScribeDb
91
+
@@ -17,7 +17,8 @@ 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
@@ -31,6 +32,7 @@ module ScribeDb
31
32
  where( content_type: [
32
33
  'image/png',
33
34
  'image/jpeg',
35
+ 'image/jpg',
34
36
  'image/gif',
35
37
  'image/webp',
36
38
  'image/svg+xml',
@@ -98,90 +100,63 @@ module ScribeDb
98
100
  .order( Arel.sql( 'COUNT(*) DESC, content_type')).count
99
101
  end
100
102
 
101
- class << self
102
- alias_method :biggest, :largest
103
- alias_method :counts_by_content_type, :content_type_counts
103
+
104
+ def self.block_counts
105
+ joins(:tx).group( 'block' )
106
+ .order( 'block').count
107
+ end
108
+
109
+ def self.block_with_timestamp_counts
110
+ joins(:tx).group( Arel.sql( "block || ' @ ' || date" ))
111
+ .order( 'block' ).count
112
+ end
113
+
114
+
115
+ def self.date_counts
116
+ ## note: strftime is SQLite specific/only!!!
117
+ joins(:tx).group( Arel.sql("strftime('%Y-%m-%d', date)"))
118
+ .order( Arel.sql("strftime('%Y-%m-%d', date)")).count
119
+ end
120
+
121
+ def self.month_counts
122
+ ## note: strftime is SQLite specific/only!!!
123
+ joins(:tx).group( Arel.sql("strftime('%Y-%m', date)"))
124
+ .order( Arel.sql("strftime('%Y-%m', date)")).count
104
125
  end
105
126
 
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
127
+ def self.year_counts
128
+ ## note: strftime is SQLite specific/only!!!
129
+ joins(:tx).group( Arel.sql("strftime('%Y', date)"))
130
+ .order( Arel.sql("strftime('%Y', date)")).count
165
131
  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
132
+
133
+ def self.hour_counts
134
+ ## note: strftime is SQLite specific/only!!!
135
+ joins(:tx).group( Arel.sql("strftime('%Y-%m-%d %Hh', date)"))
136
+ .order( Arel.sql("strftime('%Y-%m-%d %Hh', date)")).count
181
137
  end
182
- end
183
- =end
184
138
 
139
+
140
+ def self.from_counts
141
+ ## note: from is sql keyword!!!
142
+ ## wrap in [] for sqlite - check if works for others!!!
143
+ joins(:tx).group( '[from]' )
144
+ .order( Arel.sql( 'COUNT(*) DESC')).count
145
+ end
146
+
147
+
148
+ class << self
149
+ alias_method :biggest, :largest
150
+ alias_method :counts_by_content_type, :content_type_counts
151
+ alias_method :counts_by_date, :date_counts
152
+ alias_method :counts_by_day, :date_counts
153
+ alias_method :counts_by_month, :month_counts
154
+ alias_method :counts_by_year, :year_counts
155
+ alias_method :counts_by_hour, :hour_counts
156
+ alias_method :counts_by_block, :block_counts
157
+ alias_method :counts_by_block_with_timestamp, :block_with_timestamp_counts
158
+ alias_method :counts_by_address, :from_counts
159
+ end
185
160
  end # class Scribe
186
161
 
187
162
  end # module Model
@@ -2,7 +2,7 @@
2
2
  module Scribelite
3
3
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
4
  MINOR = 2
5
- PATCH = 0
5
+ PATCH = 1
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
8
8
  def self.version
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: 0.2.1
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: 2023-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ethscribe
@@ -185,6 +185,7 @@ 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