scribelite 0.2.0 → 0.2.1

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: '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