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 +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +1 -0
- data/README.md +40 -8
- data/lib/scribelite/importer.rb +91 -0
- data/lib/scribelite/models/scribe.rb +55 -80
- data/lib/scribelite/version.rb +1 -1
- data/lib/scribelite.rb +12 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8423f47a4a80b7687f04a31e0457500faa3b52e48a40e239377f0877271aface
|
4
|
+
data.tar.gz: 86413fdd0e42317307cb97f0989fca3f8b82736539d4333687ff6c9f158f5915
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfb49887358be1a89b68c855cdce8a0c2dbafa3439f76a0ef0c8dea47ceb00316d73d82577ba01f8112a839d7dc468e312ebcc9c4e0882f7f50fd3c5b560ae9e
|
7
|
+
data.tar.gz: d77a2a72689f2e49292edbcb4c02f74b96b1135a2ccf32a33c20c6e3bd79c12288143f8237ef4e06305d9bfa95c9fd5f7934ca36d1f521284d5269c2a8460cc4
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
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
|
29
|
-
t.integer
|
30
|
-
t.string
|
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,
|
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,
|
45
|
-
t.string :to,
|
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'
|
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
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
data/lib/scribelite/version.rb
CHANGED
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.
|
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-
|
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
|