inat-channel 0.8.0.14 → 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.
@@ -1,7 +1,9 @@
1
1
  require 'set'
2
2
  require 'sanitize'
3
+
3
4
  require_relative 'config'
4
5
  require_relative 'icons'
6
+ require_relative 'template'
5
7
 
6
8
  module INatChannel
7
9
 
@@ -10,12 +12,12 @@ module INatChannel
10
12
  class << self
11
13
 
12
14
  def make_message observation
13
- [
14
- taxon_title(observation[:taxon]),
15
- observation_block(observation),
16
- "#{INatChannel::Icons::ICONS[:location]} #{geo_link(observation)}\n" + (place_block(observation[:place_ids]) || observation[:place_guess]),
17
- ancestors_block(observation)
18
- ].join("\n\n")
15
+ template = if IC::CONFIG.dig(:tg_bot, :template)
16
+ IC::load_template IC::CONFIG.dig(:tg_bot, :template)
17
+ else
18
+ IC::default_template
19
+ end
20
+ template.process observation
19
21
  end
20
22
 
21
23
  def list_photos observation
@@ -23,117 +25,23 @@ module INatChannel
23
25
  observation[:photos].map { |ph| ph[:url].gsub("square", "large") }
24
26
  end
25
27
 
26
- private
27
-
28
- def taxon_title taxon
29
- icon = INatChannel::Icons::taxon_icon taxon
30
- link = "https://www.inaturalist.org/taxa/#{taxon[:id]}"
31
-
32
- common_name = taxon[:preferred_common_name]
33
- scientific_name = taxon[:name]
34
-
35
- title = if common_name
36
- "<b>#{common_name}</b> <i>(#{scientific_name})</i>"
37
- else
38
- "<b><i>#{scientific_name}</i></b>"
39
- end
40
-
41
- "#{icon} <a href='#{link}'>#{title}</a>"
42
- end
43
-
44
- def observation_block observation
45
- user = observation[:user]
46
- user_title = if user[:name] && !user[:name].empty?
47
- user[:name]
48
- else
49
- "<code>#{user[:login]}</code>"
50
- end
51
- user_link = "https://www.inaturalist.org/people/#{user[:login]}"
52
- observation_part = "#{INatChannel::Icons::ICONS[:observation]} <a href='#{observation[:uri]}'><b>\##{observation[:id]}</b></a>"
53
- user_part = "#{INatChannel::Icons::ICONS[:user]} <a href='#{user_link}'>#{user_title}</a>"
54
- date_part = "#{INatChannel::Icons::ICONS[:calendar]} #{observation[:observed_on_string]}"
55
- description = observation[:description] && Sanitize.fragment(observation[:description])
56
- description_part = if description && !description.empty?
57
- "\n#{INatChannel::Icons::ICONS[:description]} #{limit_text(description, 500)}"
58
- else
59
- ""
60
- end
61
- "#{observation_part}\n#{date_part}\n#{user_part}#{description_part}"
62
- end
63
-
64
- def limit_text text, limit
65
- return text if text.length <= limit
66
- truncated = text[0, limit]
67
- last_space = truncated.rindex(/\s/)
68
- last_sign = truncated.rindex(/[,.;:!?]/)
69
- if last_space
70
- if last_sign && last_sign + 1 > last_space
71
- return truncated[0, last_sign + 1] + '...'
72
- end
73
- return truncated[0, last_space] + '...'
74
- else
75
- if last_sign
76
- return truncated[0, last_sign + 1] + '...'
77
- end
78
- return truncated + '...'
79
- end
80
- end
81
-
82
- def place_block place_ids
83
- return nil unless CONFIG[:places]
84
-
85
- place_ids = Set[*place_ids]
86
- found = []
87
- CONFIG[:places].each do |_, list|
88
- list.each do |item|
89
- item_ids = Set[*item[:place_ids]]
90
- if place_ids.intersect?(item_ids)
91
- found << item
92
- break
93
- end
94
- end
95
- end
96
-
97
- if found.empty?
98
- nil
99
- else
100
- found.map { |i| "#{INatChannel::Icons::ICONS[:place]} <a href='#{i[:link]}'>#{i[:text]}</a>" }.join("\n")
101
- end
102
- end
103
-
104
- def ancestors_block observation
105
- taxon_id = observation[:taxon][:id]
106
- ancestors = nil
107
- observation[:identifications].each do |ident|
108
- it = ident[:taxon]
109
- if it[:id] == taxon_id
110
- ancestors = it[:ancestors]
111
- break
112
- end
113
- end
114
-
115
- if ancestors
116
- (ancestors.map { |a| name_to_hashtag(a[:name]) } + [name_to_hashtag(observation[:taxon][:name])]).join(" • ")
117
- else
118
- # TODO: load ancestors with new query...
119
- nil
120
- end
121
- end
28
+ end
122
29
 
123
- def name_to_hashtag name
124
- "\##{name.gsub(".", "").gsub("-", "").gsub(" ", "_")}"
125
- end
30
+ end
126
31
 
127
- def geo_link observation
128
- return nil unless observation[:geojson]&.[](:coordinates) && observation[:geojson][:type] == "Point"
32
+ end
129
33
 
130
- lon, lat = observation[:geojson][:coordinates]
131
- url = "https://maps.google.com/?q=#{lat},#{lon}"
132
- "<a href='#{url}'>#{lat.round(4)}°N, #{lon.round(4)}°E</a>"
133
- end
34
+ module IC
134
35
 
135
- end
36
+ def make_message observation
37
+ INatChannel::Message::make_message observation
38
+ end
136
39
 
40
+ def list_photos observation
41
+ INatChannel::Message::list_photos observation
137
42
  end
138
43
 
44
+ module_function :make_message, :list_photos
45
+
139
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 = INatChannel::Message::list_photos observation
15
- message = INatChannel::Message::make_message observation
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 INatChannel::CONFIG[:chat_id], photos[0..9], message
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 INatChannel::CONFIG[:chat_id], message
20
+ msg_id = send_message IC::CONFIG.dig(:tg_bot, :chat_id), message
21
21
  end
22
22
 
23
- INatChannel::Data::sent[observation[:uuid]] = { msg_id: msg_id, sent_at: Time.now.to_s }
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(INatChannel::CONFIG[:admin_telegram_id], "iNatChannel: #{text}")
28
+ send_message(IC::CONFIG.dig(:tg_bot, :admin_id), "iNatChannel: #{text}")
30
29
  rescue
31
- INatChannel::LOGGER.error "Admin notify failed"
30
+ IC::logger.error "Admin notify failed"
32
31
  end
33
32
 
34
33
  private
35
34
 
36
35
  def token
37
- @token ||= INatChannel::CONFIG[:telegram_bot_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, max: INatChannel::CONFIG[:retries], interval: 2.0, interval_randomness: 0.5,
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 INatChannel::LOGGER.level == ::Logger::DEBUG
78
- f.response :logger, INatChannel::LOGGER, bodies: true, headers: true
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
+
@@ -0,0 +1,106 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require_relative 'data_types'
5
+ require_relative 'data_convert'
6
+
7
+ module INatChannel
8
+
9
+ class Template
10
+
11
+ attr_reader :template, :data
12
+
13
+ def initialize template, data
14
+ @template = template
15
+ @data = data
16
+ @renderer = ERB::new @template, trim_mode: '-'
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
+ end
21
+
22
+ def process observation_source
23
+ observation = IC::convert_observation observation_source
24
+ vars = {
25
+ observation: observation,
26
+ datetime: observation.datetime,
27
+ location: observation.location,
28
+ places: observation.places,
29
+ taxon: observation.taxon,
30
+ user: observation.user,
31
+ date: observation.date,
32
+ time: observation.time,
33
+ icons: IC::ICONS,
34
+ taxa_icons: IC::TAXA_ICONS,
35
+ config: IC::CONFIG,
36
+ data: @data
37
+ }
38
+ @renderer.result_with_hash vars
39
+ end
40
+
41
+ class << self
42
+
43
+ def load path
44
+ content = File.read path
45
+ if content.lines(chomp: true).first == '---'
46
+ docs = content.split(/^---\n/m, 3)
47
+ data = if docs[0].strip.empty? && docs[1]
48
+ YAML.safe_load docs[1], symbolize_names: true
49
+ else
50
+ YAML.safe_load docs[0], symbolize_names: true
51
+ end || {}
52
+ template = docs[2] || docs[1] || content
53
+ else
54
+ data = {}
55
+ template = content
56
+ end
57
+ new(template, data)
58
+ end
59
+
60
+ DEFAULT_TEMPLATE = <<~ERB
61
+ <%= taxon.icon %> <a href="<%= taxon.url %>"><%= taxon.title %></a>
62
+
63
+ <%= observation.icon %> <%= observation.url %>
64
+ <%= datetime.icon %> <%= datetime %>
65
+ <%= user.icon %> <a href="<%= user.url %>"><%= user.title %></a>
66
+ <% if observation.description -%>
67
+ <blockquote><%= observation.description.text %></blockquote>
68
+ <% end -%>
69
+
70
+ <%= location.icon %> <%= location.dms %> • <a href="<%= location.google %>">G</a> <a href="<%= location.osm %>">OSM</a>
71
+ <% if places && places.size > 0 -%>
72
+ <% places.each do |place| -%>
73
+ <%= place.icon %> <a href="<%= place.link %>"><%= place.text %></a> <%= '• #' + place.tag if place.tag %>
74
+ <% end -%>
75
+ <% else -%>
76
+ <%= icons[:place] %> <%= observation.place_guess %>
77
+ <% end -%>
78
+
79
+ <%= taxon.to_tags&.join(' • ') %>
80
+ ERB
81
+
82
+ private_constant :DEFAULT_TEMPLATE
83
+
84
+ def default
85
+ @default ||= new(DEFAULT_TEMPLATE, {}).freeze
86
+ end
87
+
88
+ private :new
89
+
90
+ end
91
+
92
+ end
93
+
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
@@ -1,6 +1,11 @@
1
-
2
1
  module INatChannel
2
+
3
+ VERSION = '0.9.0'
4
+
5
+ end
3
6
 
4
- VERSION = '0.8.0.14'
7
+ module IC
8
+
9
+ VERSION = INatChannel::VERSION
5
10
 
6
11
  end
data/lib/inat-channel.rb CHANGED
@@ -8,4 +8,3 @@ require_relative 'inat-channel/api'
8
8
  require_relative 'inat-channel/message'
9
9
  require_relative 'inat-channel/telegram'
10
10
 
11
- INCh = INatChannel
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.8.0.14
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:
@@ -151,11 +123,14 @@ files:
151
123
  - lib/inat-channel/api.rb
152
124
  - lib/inat-channel/config.rb
153
125
  - lib/inat-channel/data.rb
126
+ - lib/inat-channel/data_convert.rb
127
+ - lib/inat-channel/data_types.rb
154
128
  - lib/inat-channel/icons.rb
155
129
  - lib/inat-channel/lock.rb
156
130
  - lib/inat-channel/logger.rb
157
131
  - lib/inat-channel/message.rb
158
132
  - lib/inat-channel/telegram.rb
133
+ - lib/inat-channel/template.rb
159
134
  - lib/inat-channel/version.rb
160
135
  homepage: https://github.com/inat-get/inat-channel
161
136
  licenses: