inat-channel 0.8.2 → 0.9.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 +4 -4
- data/README.md +327 -94
- data/bin/inat-channel +14 -15
- data/lib/inat-channel/api.rb +53 -30
- data/lib/inat-channel/config.rb +52 -13
- data/lib/inat-channel/data.rb +267 -36
- data/lib/inat-channel/data_convert.rb +27 -11
- data/lib/inat-channel/data_types.rb +53 -81
- data/lib/inat-channel/icons.rb +21 -0
- data/lib/inat-channel/lock.rb +6 -6
- data/lib/inat-channel/logger.rb +10 -2
- data/lib/inat-channel/message.rb +18 -3
- data/lib/inat-channel/telegram.rb +30 -12
- data/lib/inat-channel/template.rb +25 -9
- data/lib/inat-channel/version.rb +7 -2
- data/lib/inat-channel.rb +0 -1
- metadata +1 -29
|
@@ -4,56 +4,21 @@ require 'sanitize'
|
|
|
4
4
|
|
|
5
5
|
require_relative 'icons'
|
|
6
6
|
|
|
7
|
-
module INatChannel
|
|
8
|
-
FORMATS = {
|
|
9
|
-
date: '%Y.%m.%d',
|
|
10
|
-
time: '%H:%M %Z',
|
|
11
|
-
datetime: '%Y.%m.%d %H:%M %Z',
|
|
12
|
-
location: :DMS, # or :decimal
|
|
13
|
-
zoom: 12,
|
|
14
|
-
description_limit: 512
|
|
15
|
-
}
|
|
16
|
-
end
|
|
17
|
-
|
|
18
7
|
class Date
|
|
19
8
|
def icon
|
|
20
|
-
|
|
21
|
-
end
|
|
22
|
-
def to_s
|
|
23
|
-
fmt = INatChannel::FORMATS[:date]
|
|
24
|
-
if fmt
|
|
25
|
-
self.strftime fmt
|
|
26
|
-
else
|
|
27
|
-
super
|
|
28
|
-
end
|
|
9
|
+
IC::ICONS[:calendar]
|
|
29
10
|
end
|
|
30
11
|
end
|
|
31
12
|
|
|
32
13
|
class Time
|
|
33
14
|
def icon
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
def to_s
|
|
37
|
-
fmt = INatChannel::FORMATS[:time]
|
|
38
|
-
if fmt
|
|
39
|
-
self.strftime fmt
|
|
40
|
-
else
|
|
41
|
-
super
|
|
42
|
-
end
|
|
15
|
+
IC::clock_icon self
|
|
43
16
|
end
|
|
44
17
|
end
|
|
45
18
|
|
|
46
19
|
class DateTime
|
|
47
20
|
def icon
|
|
48
|
-
|
|
49
|
-
end
|
|
50
|
-
def to_s
|
|
51
|
-
fmt = INatChannel::FORMATS[:datetime]
|
|
52
|
-
if fmt
|
|
53
|
-
self.strftime fmt
|
|
54
|
-
else
|
|
55
|
-
super
|
|
56
|
-
end
|
|
21
|
+
IC::ICONS[:calendar]
|
|
57
22
|
end
|
|
58
23
|
end
|
|
59
24
|
|
|
@@ -93,9 +58,9 @@ class Array
|
|
|
93
58
|
end
|
|
94
59
|
end
|
|
95
60
|
|
|
96
|
-
Observation =
|
|
61
|
+
Observation = Data::define :taxon, :id, :uuid, :url, :user, :datetime, :places, :place_guess, :description, :location do
|
|
97
62
|
def icon
|
|
98
|
-
|
|
63
|
+
IC::ICONS[:observation]
|
|
99
64
|
end
|
|
100
65
|
def date
|
|
101
66
|
datetime.to_date
|
|
@@ -105,9 +70,9 @@ Observation = Struct::new :taxon, :id, :uuid, :url, :user, :datetime, :places, :
|
|
|
105
70
|
end
|
|
106
71
|
end
|
|
107
72
|
|
|
108
|
-
Taxon =
|
|
73
|
+
Taxon = Data::define :scientific_name, :common_name, :id, :ancestors do
|
|
109
74
|
def icon
|
|
110
|
-
|
|
75
|
+
IC::ancestors_icon ancestors.map(&:id)
|
|
111
76
|
end
|
|
112
77
|
def title
|
|
113
78
|
if common_name && !common_name.empty?
|
|
@@ -124,15 +89,15 @@ Taxon = Struct::new :scientific_name, :common_name, :id, :ancestors, keyword_ini
|
|
|
124
89
|
end
|
|
125
90
|
end
|
|
126
91
|
|
|
127
|
-
Ancestor =
|
|
92
|
+
Ancestor = Data::define :scientific_name, :id do
|
|
128
93
|
def to_tag
|
|
129
94
|
scientific_name.to_tag
|
|
130
95
|
end
|
|
131
96
|
end
|
|
132
97
|
|
|
133
|
-
Place =
|
|
98
|
+
Place = Data::define :text, :link, :tag do
|
|
134
99
|
def icon
|
|
135
|
-
|
|
100
|
+
IC::ICONS[:place]
|
|
136
101
|
end
|
|
137
102
|
def to_tag
|
|
138
103
|
tag&.to_tag
|
|
@@ -145,9 +110,9 @@ Place = Struct::new :ids, :text, :link, :tag, keyword_init: true do
|
|
|
145
110
|
end
|
|
146
111
|
end
|
|
147
112
|
|
|
148
|
-
User =
|
|
113
|
+
User = Data::define :id, :login, :name do
|
|
149
114
|
def icon
|
|
150
|
-
|
|
115
|
+
IC::ICONS[:user]
|
|
151
116
|
end
|
|
152
117
|
def title
|
|
153
118
|
if name && !name.empty?
|
|
@@ -161,28 +126,32 @@ User = Struct::new :id, :login, :name, keyword_init: true do
|
|
|
161
126
|
end
|
|
162
127
|
end
|
|
163
128
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
129
|
+
module IC
|
|
130
|
+
|
|
131
|
+
SANITIZE_HTML_CONFIG = {
|
|
132
|
+
elements: [ 'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'del', 'a', 'code', 'pre', 'tg-spoiler', 'blockquote' ],
|
|
133
|
+
attributes: { 'a' => [ 'href' ] },
|
|
134
|
+
protocols: { 'a' => { 'href' => [ 'http', 'https', 'mailto', 'tg' ] } },
|
|
135
|
+
remove_contents: [ 'script', 'style' ]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
SANITIZE_TEXT_CONFIG = {
|
|
139
|
+
elements: [],
|
|
140
|
+
remove_contents: [ 'script', 'style' ]
|
|
141
|
+
}
|
|
170
142
|
|
|
171
|
-
|
|
172
|
-
elements: [],
|
|
173
|
-
remove_contents: [ 'script', 'style' ]
|
|
174
|
-
}
|
|
143
|
+
end
|
|
175
144
|
|
|
176
|
-
Description =
|
|
145
|
+
Description = Data::define :value do
|
|
177
146
|
def icon
|
|
178
|
-
|
|
147
|
+
IC::ICONS[:description]
|
|
179
148
|
end
|
|
180
149
|
def text
|
|
181
|
-
Sanitize.fragment(value, SANITIZE_TEXT_CONFIG).limit(
|
|
150
|
+
Sanitize.fragment(value, IC::SANITIZE_TEXT_CONFIG).limit(IC::CONFIG.dig(:tg_bot, :desc_limit))
|
|
182
151
|
end
|
|
183
152
|
def html
|
|
184
|
-
sanitized = Sanitize.fragment value, SANITIZE_HTML_CONFIG
|
|
185
|
-
if sanitized.length >
|
|
153
|
+
sanitized = Sanitize.fragment value, IC::SANITIZE_HTML_CONFIG
|
|
154
|
+
if sanitized.length > IC::CONFIG.dig(:tg_bot, :desc_limit)
|
|
186
155
|
# В отличие от простого текста, обрезка HTML требует куда более изощренной логики, что неоправданно
|
|
187
156
|
text
|
|
188
157
|
else
|
|
@@ -191,36 +160,39 @@ Description = Struct::new :value, keyword_init: true do
|
|
|
191
160
|
end
|
|
192
161
|
end
|
|
193
162
|
|
|
194
|
-
Location =
|
|
163
|
+
Location = Data::define :lat, :lng do
|
|
195
164
|
def icon
|
|
196
|
-
|
|
165
|
+
IC::ICONS[:location]
|
|
197
166
|
end
|
|
198
|
-
def
|
|
199
|
-
lat_dir = lat >= 0 ?
|
|
200
|
-
lng_dir = lng >= 0 ?
|
|
167
|
+
def dms
|
|
168
|
+
lat_dir = lat >= 0 ? "N" : "S"
|
|
169
|
+
lng_dir = lng >= 0 ? "E" : "W"
|
|
201
170
|
lat_abs = lat.abs
|
|
202
171
|
lng_abs = lng.abs
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
172
|
+
lat_d = lat_abs.floor
|
|
173
|
+
lat_m = ((lat_abs - lat_d) * 60).floor
|
|
174
|
+
lat_s = ((lat_abs - lat_d - lat_m / 60.0) * 3600).round
|
|
175
|
+
lng_d = lng_abs.floor
|
|
176
|
+
lng_m = ((lng_abs - lng_d) * 60).floor
|
|
177
|
+
lng_s = ((lng_abs - lng_d - lng_m / 60.0) * 3600).round
|
|
178
|
+
"%d°%02d'%02d\"%s %d°%02d'%02d\"%s" % [lat_d, lat_m, lat_s, lat_dir, lng_d, lng_m, lng_s, lng_dir]
|
|
179
|
+
end
|
|
180
|
+
def decimal
|
|
181
|
+
lat_dir = lat >= 0 ? "N" : "S"
|
|
182
|
+
lng_dir = lng >= 0 ? "E" : "W"
|
|
183
|
+
lat_abs = lat.abs
|
|
184
|
+
lng_abs = lng.abs
|
|
185
|
+
"%.4f°%s, %.4f°%s" % [lat_abs, lat_dir, lng_abs, lng_dir]
|
|
214
186
|
end
|
|
215
187
|
def google
|
|
216
188
|
# "https://www.google.com/maps/search/?api=1&query=#{lat},#{lng}&z=#{FORMATS[:zoom]}&ll=#{lat},#{lng}"
|
|
217
|
-
"https://www.google.com/maps/place/#{lat},#{lng}/@#{lat},#{lng},#{
|
|
189
|
+
"https://www.google.com/maps/place/#{lat},#{lng}/@#{lat},#{lng},#{IC::CONFIG.dig(:tg_bot, :link_zoom)}z/"
|
|
218
190
|
end
|
|
219
191
|
def yandex
|
|
220
|
-
"https://yandex.ru/maps/?ll=#{lng},#{lat}&z=#{
|
|
192
|
+
"https://yandex.ru/maps/?ll=#{lng},#{lat}&z=#{IC::CONFIG.dig(:tg_bot, :link_zoom)}&pt=#{lng},#{lat},pm2rdm1"
|
|
221
193
|
end
|
|
222
194
|
def osm
|
|
223
|
-
"https://www.openstreetmap.org/?mlat=#{lat}&mlon=#{lng}#map=#{
|
|
195
|
+
"https://www.openstreetmap.org/?mlat=#{lat}&mlon=#{lng}#map=#{IC::CONFIG.dig(:tg_bot, :link_zoom)}/#{lat}/#{lng}"
|
|
224
196
|
end
|
|
225
197
|
def url
|
|
226
198
|
osm
|
data/lib/inat-channel/icons.rb
CHANGED
|
@@ -112,3 +112,24 @@ module INatChannel
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
end
|
|
115
|
+
|
|
116
|
+
module IC
|
|
117
|
+
|
|
118
|
+
TAXA_ICONS = INatChannel::Icons::TAXA_ICONS
|
|
119
|
+
ICONS = INatChannel::Icons::ICONS
|
|
120
|
+
|
|
121
|
+
def taxon_icon taxon
|
|
122
|
+
INatChannel::Icons::taxon_icon taxon
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ancestors_icon ancestor_ids
|
|
126
|
+
INatChannel::Icons::ancestors_icon ancestor_ids
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def clock_icon time
|
|
130
|
+
INatChannel::Icons::clock_icon time
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module_function :taxon_icon, :ancestors_icon, :clock_icon
|
|
134
|
+
|
|
135
|
+
end
|
data/lib/inat-channel/lock.rb
CHANGED
|
@@ -12,13 +12,13 @@ module INatChannel
|
|
|
12
12
|
class << self
|
|
13
13
|
|
|
14
14
|
def acquire!
|
|
15
|
-
file =
|
|
15
|
+
file = IC::CONFIG.dig(:lock_file, :path)
|
|
16
16
|
FileUtils.mkdir_p File.dirname(file)
|
|
17
17
|
|
|
18
18
|
if File.exist?(file)
|
|
19
19
|
data = load_data file
|
|
20
20
|
if stale?(data)
|
|
21
|
-
|
|
21
|
+
IC::logger.info "Remove stale lock: #{file}"
|
|
22
22
|
File.delete file
|
|
23
23
|
else
|
|
24
24
|
raise "Another instance is already running (PID: #{data[:pid]})"
|
|
@@ -30,15 +30,15 @@ module INatChannel
|
|
|
30
30
|
started_at: Time.now.utc.iso8601
|
|
31
31
|
}
|
|
32
32
|
File.write file, JSON.pretty_generate(data)
|
|
33
|
-
|
|
33
|
+
IC::logger.info "Lock acquired: #{file}"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def release!
|
|
37
|
-
file =
|
|
37
|
+
file = IC::CONFIG.dig(:lock_file, :path)
|
|
38
38
|
return nil unless File.exist?(file)
|
|
39
39
|
|
|
40
40
|
File.delete file
|
|
41
|
-
|
|
41
|
+
IC::logger.info "Lock release: #{file}"
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
@@ -49,7 +49,7 @@ module INatChannel
|
|
|
49
49
|
{}
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
LOCK_TTL =
|
|
52
|
+
LOCK_TTL = 300 # 5 min
|
|
53
53
|
|
|
54
54
|
def stale? data
|
|
55
55
|
if data[:started_at]
|
data/lib/inat-channel/logger.rb
CHANGED
|
@@ -14,7 +14,7 @@ module INatChannel
|
|
|
14
14
|
|
|
15
15
|
def get_logger
|
|
16
16
|
lgr = ::Logger::new $stderr
|
|
17
|
-
lgr.level =
|
|
17
|
+
lgr.level = IC::CONFIG[:log_level]
|
|
18
18
|
lgr
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -22,7 +22,15 @@ module INatChannel
|
|
|
22
22
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module IC
|
|
28
|
+
|
|
29
|
+
def logger
|
|
30
|
+
INatChannel::Logger::logger
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module_function :logger
|
|
26
34
|
|
|
27
35
|
end
|
|
28
36
|
|
data/lib/inat-channel/message.rb
CHANGED
|
@@ -12,10 +12,10 @@ module INatChannel
|
|
|
12
12
|
class << self
|
|
13
13
|
|
|
14
14
|
def make_message observation
|
|
15
|
-
template = if
|
|
16
|
-
|
|
15
|
+
template = if IC::CONFIG.dig(:tg_bot, :template)
|
|
16
|
+
IC::load_template IC::CONFIG.dig(:tg_bot, :template)
|
|
17
17
|
else
|
|
18
|
-
|
|
18
|
+
IC::default_template
|
|
19
19
|
end
|
|
20
20
|
template.process observation
|
|
21
21
|
end
|
|
@@ -30,3 +30,18 @@ module INatChannel
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
module IC
|
|
35
|
+
|
|
36
|
+
def make_message observation
|
|
37
|
+
INatChannel::Message::make_message observation
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list_photos observation
|
|
41
|
+
INatChannel::Message::list_photos observation
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module_function :make_message, :list_photos
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
@@ -11,30 +11,29 @@ module INatChannel
|
|
|
11
11
|
TELEGRAM_API = 'https://api.telegram.org/bot'
|
|
12
12
|
|
|
13
13
|
def send_observation observation
|
|
14
|
-
photos =
|
|
15
|
-
message =
|
|
14
|
+
photos = IC::list_photos observation
|
|
15
|
+
message = IC::make_message observation
|
|
16
16
|
|
|
17
17
|
unless photos.empty?
|
|
18
|
-
msg_id = send_media_group
|
|
18
|
+
msg_id = send_media_group IC::CONFIG.dig(:tg_bot, :chat_id), photos[0..9], message
|
|
19
19
|
else
|
|
20
|
-
msg_id = send_message
|
|
20
|
+
msg_id = send_message IC::CONFIG.dig(:tg_bot, :chat_id), message
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
INatChannel::LOGGER.info "Posted #{observation[:id]} (#{photos.size} photos)"
|
|
23
|
+
IC::logger.info "Posted #{observation[:id]} (#{photos.size} photos)"
|
|
25
24
|
msg_id
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def notify_admin text
|
|
29
|
-
send_message(
|
|
28
|
+
send_message(IC::CONFIG.dig(:tg_bot, :admin_id), "iNatChannel: #{text}")
|
|
30
29
|
rescue
|
|
31
|
-
|
|
30
|
+
IC::logger.error "Admin notify failed"
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
private
|
|
35
34
|
|
|
36
35
|
def token
|
|
37
|
-
@token ||=
|
|
36
|
+
@token ||= IC::CONFIG.dig(:tg_bot, :token)
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
def send_message chat_id, text
|
|
@@ -71,11 +70,15 @@ module INatChannel
|
|
|
71
70
|
|
|
72
71
|
def faraday
|
|
73
72
|
@faraday ||= Faraday.new do |f|
|
|
74
|
-
f.request :retry,
|
|
73
|
+
f.request :retry,
|
|
74
|
+
max: IC::CONFIG.dig(:tg_bot, :retries),
|
|
75
|
+
interval: IC::CONFIG.dig(:tg_bot, :interval),
|
|
76
|
+
interval_randomness: IC::CONFIG.dig(:tg_bot, :randomness),
|
|
77
|
+
backoff_factor: IC::CONFIG.dig(:tg_bot, :backoff),
|
|
75
78
|
exceptions: [ Faraday::TimeoutError, Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ClientError ]
|
|
76
79
|
|
|
77
|
-
if
|
|
78
|
-
f.response :logger,
|
|
80
|
+
if IC::logger.level == ::Logger::DEBUG
|
|
81
|
+
f.response :logger, IC::logger, bodies: true, headers: true
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
f.adapter Faraday.default_adapter
|
|
@@ -87,3 +90,18 @@ module INatChannel
|
|
|
87
90
|
end
|
|
88
91
|
|
|
89
92
|
end
|
|
93
|
+
|
|
94
|
+
module IC
|
|
95
|
+
|
|
96
|
+
def send_observation observation
|
|
97
|
+
INatChannel::Telegram::send_observation observation
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def notify_admin text
|
|
101
|
+
INatChannel::Telegram::notify_admin text
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module_function :send_observation, :notify_admin
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
|
|
@@ -14,13 +14,13 @@ module INatChannel
|
|
|
14
14
|
@template = template
|
|
15
15
|
@data = data
|
|
16
16
|
@renderer = ERB::new @template, trim_mode: '-'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
IC::TAXA_ICONS.merge! data[:taxa_icons] if data[:taxa_icons]
|
|
18
|
+
IC::ICONS.merge! data[:icons] if data[:icons]
|
|
19
|
+
IC::FORMATS.merge! data[:formats] if data[:formats]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def process observation_source
|
|
23
|
-
observation =
|
|
23
|
+
observation = IC::convert_observation observation_source
|
|
24
24
|
vars = {
|
|
25
25
|
observation: observation,
|
|
26
26
|
datetime: observation.datetime,
|
|
@@ -30,8 +30,10 @@ module INatChannel
|
|
|
30
30
|
user: observation.user,
|
|
31
31
|
date: observation.date,
|
|
32
32
|
time: observation.time,
|
|
33
|
-
icons:
|
|
34
|
-
taxa_icons:
|
|
33
|
+
icons: IC::ICONS,
|
|
34
|
+
taxa_icons: IC::TAXA_ICONS,
|
|
35
|
+
config: IC::CONFIG,
|
|
36
|
+
data: @data
|
|
35
37
|
}
|
|
36
38
|
@renderer.result_with_hash vars
|
|
37
39
|
end
|
|
@@ -58,14 +60,14 @@ module INatChannel
|
|
|
58
60
|
DEFAULT_TEMPLATE = <<~ERB
|
|
59
61
|
<%= taxon.icon %> <a href="<%= taxon.url %>"><%= taxon.title %></a>
|
|
60
62
|
|
|
61
|
-
<%= observation.icon %>
|
|
63
|
+
<%= observation.icon %> <%= observation.url %>
|
|
62
64
|
<%= datetime.icon %> <%= datetime %>
|
|
63
65
|
<%= user.icon %> <a href="<%= user.url %>"><%= user.title %></a>
|
|
64
66
|
<% if observation.description -%>
|
|
65
67
|
<blockquote><%= observation.description.text %></blockquote>
|
|
66
68
|
<% end -%>
|
|
67
69
|
|
|
68
|
-
<%= location.icon %> <%= location.
|
|
70
|
+
<%= location.icon %> <%= location.dms %> • <a href="<%= location.google %>">G</a> <a href="<%= location.osm %>">OSM</a>
|
|
69
71
|
<% if places && places.size > 0 -%>
|
|
70
72
|
<% places.each do |place| -%>
|
|
71
73
|
<%= place.icon %> <a href="<%= place.link %>"><%= place.text %></a> <%= '• #' + place.tag if place.tag %>
|
|
@@ -74,9 +76,11 @@ module INatChannel
|
|
|
74
76
|
<%= icons[:place] %> <%= observation.place_guess %>
|
|
75
77
|
<% end -%>
|
|
76
78
|
|
|
77
|
-
<%= taxon.to_tags
|
|
79
|
+
<%= taxon.to_tags&.join(' • ') %>
|
|
78
80
|
ERB
|
|
79
81
|
|
|
82
|
+
private_constant :DEFAULT_TEMPLATE
|
|
83
|
+
|
|
80
84
|
def default
|
|
81
85
|
@default ||= new(DEFAULT_TEMPLATE, {}).freeze
|
|
82
86
|
end
|
|
@@ -88,3 +92,15 @@ module INatChannel
|
|
|
88
92
|
end
|
|
89
93
|
|
|
90
94
|
end
|
|
95
|
+
|
|
96
|
+
module IC
|
|
97
|
+
|
|
98
|
+
def load_template path
|
|
99
|
+
INatChannel::Template::load path
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def default_template
|
|
103
|
+
INatChannel::Template::default
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
data/lib/inat-channel/version.rb
CHANGED
data/lib/inat-channel.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inat-channel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Shikhalev
|
|
@@ -107,34 +107,6 @@ dependencies:
|
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '3.23'
|
|
110
|
-
- !ruby/object:Gem::Dependency
|
|
111
|
-
name: tmpdir
|
|
112
|
-
requirement: !ruby/object:Gem::Requirement
|
|
113
|
-
requirements:
|
|
114
|
-
- - "~>"
|
|
115
|
-
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.2.0
|
|
117
|
-
type: :development
|
|
118
|
-
prerelease: false
|
|
119
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
-
requirements:
|
|
121
|
-
- - "~>"
|
|
122
|
-
- !ruby/object:Gem::Version
|
|
123
|
-
version: 0.2.0
|
|
124
|
-
- !ruby/object:Gem::Dependency
|
|
125
|
-
name: climate_control
|
|
126
|
-
requirement: !ruby/object:Gem::Requirement
|
|
127
|
-
requirements:
|
|
128
|
-
- - "~>"
|
|
129
|
-
- !ruby/object:Gem::Version
|
|
130
|
-
version: '1.2'
|
|
131
|
-
type: :development
|
|
132
|
-
prerelease: false
|
|
133
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
-
requirements:
|
|
135
|
-
- - "~>"
|
|
136
|
-
- !ruby/object:Gem::Version
|
|
137
|
-
version: '1.2'
|
|
138
110
|
description: 'iNaturalist Telegram Bot: Posts random popular observations from configurable
|
|
139
111
|
API queries.'
|
|
140
112
|
email:
|