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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f68cf20d6a12a86d06bf3fdfbe2f8e61d6890060275e713e3e1ef530404a05d8
4
- data.tar.gz: feae77da45b8ae5cc418407e06b3179891764fc9def665b48f8101499029335d
3
+ metadata.gz: af5a4910d9c67e726bc96582469fb905101392ab37612bff1795e8165d31f53a
4
+ data.tar.gz: ea75e7e0b0c406666ff502536e6cb9554db7a55ae778c7feec98964110c259a5
5
5
  SHA512:
6
- metadata.gz: 4316d7ba2c98e45666a52b747c3eed6be62a09ca3f18253f09060cdc355f2f4c9c73b16add00481459262cec06290ea984ad6c14a27f2bd238f9d386f565f909
7
- data.tar.gz: fdd98d39bb81f9999968a6ab2577ef208eccf9ff847988d1eff07051d39108e3c86b14e17dad40ae58573fcc11b3ba16cb4e357712a9fe01f27f186bf3d1e21c
6
+ metadata.gz: d013cf9b14e8bf324ffa054b3f0a550fa8fe19259d619f227c82f7599e848ab983488c9749eb7a849ef4c75721a27378f00e74e90220bfeb5db8a6d99a345e9d
7
+ data.tar.gz: f95460b1c54e594e82d638a9e4291024c0f53992a977330f16a6ca864e060b6c6bc52a890bc18fc3e1846c2dca9feaed75cffb3ef2ea4992b2e6f87709f14f2e
data/README.md CHANGED
@@ -1,150 +1,383 @@
1
- # iNat Telegram Poster
1
+ | **EN** | [ru](README-ru.md) |
2
+ |----------|----------|
3
+
4
+ # inat-channel — automatic posting of observations
2
5
 
3
6
  [![GitHub License](https://img.shields.io/github/license/inat-get/inat-channel)](LICENSE)
4
- [![Gem Version](https://badge.fury.io/rb/inat-channel.svg?icon=si%3Arubygems&d=3)](https://badge.fury.io/rb/inat-channel)
7
+ [![Gem Version](https://badge.fury.io/rb/inat-channel.svg?icon=si%3Arubygems&d=6)](https://badge.fury.io/rb/inat-channel)
5
8
  [![Ruby](https://github.com/inat-get/inat-channel/actions/workflows/ruby.yml/badge.svg)](https://github.com/inat-get/inat-channel/actions/workflows/ruby.yml)
6
9
  ![Coverage](coverage-badge.svg)
7
10
 
8
- **Автоматический бот**, который ежедневно публикует в Telegram-каналы случайные, популярные наблюдения из iNaturalist, согласно гибким настройкам API-запросов.
11
+ A script that sends a random iNaturalist observation from a selected sample to Telegram.
12
+
13
+ ## What does it do?
14
+
15
+ + Obtains a sample via the [iNaturalist API](https://api.inaturalist.org/v2/docs/#/Observations/get_observations) based on an arbitrary query (supported by the API).
16
+
17
+ + Sends a random observation from the sample to a specified Telegram channel, excluding those already sent. If there are no new observations in the fresh sample, it takes from a saved pool.
18
+
19
+ + Saves unsent observations obtained via the query into the pool.
20
+
21
+ + Taxon uniqueness and pool depth are configurable.
22
+
23
+ + Sends a message to the administrator in case of failures.
24
+
25
+ ## Installation and running
26
+
27
+ ### Using Bundler
28
+
29
+ Create a `Gemfile` with the single line:
30
+ ```
31
+ gem 'inat-channel', '~> 0.9.0'
32
+ ```
33
+
34
+ Run:
35
+ ```
36
+ $ bundle install
37
+ ```
38
+
39
+ Then use:
40
+ ```
41
+ $ bundle exec inat-channel [options]
42
+ ```
43
+
44
+ ### Manually
45
+
46
+ Install:
47
+ ```
48
+ $ gem install inat-channel
49
+ ```
50
+
51
+ Run:
52
+ ```
53
+ $ inat-channel [options]
54
+ ```
9
55
 
10
- ## Версия
56
+ There are few command line parameters, and all are optional:
11
57
 
12
- + **0.8.0** — *предварительный* релиз.
58
+ ```
59
+ $ inat-channel --help
60
+ Usage: inat-channel [options]
61
+ -c, --config FILE Config file (default: inat-channel.yml)
62
+ -l, --log-level LEVEL Log level (default: warn)
63
+ --debug Set log level to debug
64
+ --version Show version info and exit
65
+ -h, --help Show help and exit
66
+ ```
13
67
 
14
- ## Основные возможности
68
+ ## Configuration
15
69
 
16
- - Поддержка гибких запросов iNaturalist API: проекты, таксоны, места, пользователи и др.
17
- - Очередь публикаций с приоритетом свежих наблюдений, затем пулом и архивом отправленных (без дубликатов).
18
- - Поддержка до 10 фотографий и геолокационных ссылок в одном посте.
19
- - Вывод эмодзи и хештегов, соответствующих иерархии таксонов.
20
- - Автоматическая генерация ссылок на проекты и места из настройки.
21
- - Механизм блокировки для безопасного параллельного запуска.
22
- - Автоматические повторы запросов, уведомления администратору, логирование.
70
+ Main settings are described in a YAML configuration file. Optionally, you can specify an ERB message template and extract the `places` group of settings (see below) into a separate YAML file.
23
71
 
24
- ## Быстрый старт
72
+ Most settings have default values and may be omitted, but some are mandatory.
25
73
 
26
- ```bash
27
- # 1. Установка
28
- bundle install
74
+ ### Environment variables
29
75
 
30
- # 2. Создайте конфиг (например, config.yaml)
31
- cat > config.yaml << EOF
76
+ Additionally, **two mandatory parameters** must be set **in environment variables**: `TELEGRAM_BOT_TOKEN` is responsible for the Telegram bot token,
77
+ which is issued during creation via [@BotFather](https://t.me/BotFather) you need to add this bot (your bot) to your channel as an admin
78
+ and give it rights to post messages; in the `ADMIN_TELEGRAM_ID` variable, specify your personal ID — notifications will be sent to it.
79
+ You can find this ID using the bot [@Getmyid_bot](https://t.me/Getmyid_bot). These parameters cannot be set in the config file for security reasons.
80
+
81
+ ### Configuration file
82
+
83
+ Example:
84
+ ```
32
85
  base_query:
33
- project_id: 12345
86
+ project_id: 175821
87
+ locale: ru
34
88
  popular: true
89
+ photo_license: 'cc-by,cc-by-nc,cc-by-nd,cc-by-sa,cc-by-nc-nd,cc-by-nc-sa,cc0'
35
90
  quality_grade: research
91
+
92
+ days_back:
93
+ fresh: 30
94
+ pool: 180
95
+ sent: 181
96
+ used: 360
97
+
98
+ unique_taxon: priority
99
+
100
+
101
+ lock_file:
102
+ path: data/data.lock
103
+ ttl: 300
104
+
105
+ data_files:
106
+ root: data
107
+ pool: data/pool.json
108
+ sent: data/sent.json
109
+ used: data/used.json
110
+
111
+ api:
112
+ retries: 5
113
+ interval: 1.0
114
+ randomness: 0.5
115
+ backoff: 2
116
+ page_delay: 1.0
117
+ per_page: 200
118
+
119
+ tg_bot:
120
+ retries: 5
121
+ interval: 1.0
122
+ randomness: 0.5
123
+ backoff: 2
124
+ chat_id: '@<my_test_channel>'
125
+ template: message.erb
126
+ desc_limit: 512
127
+ link_zoom: 12
128
+
129
+ log_level: info
130
+ notify_level: warn
131
+
132
+
133
+ places: places.yml
134
+ ```
135
+
136
+ This is a fully working (if you fix `tg_bot.chat_id`) but somewhat verbose config. Let's break down the parameters.
137
+
138
+ #### API query parameters
139
+
140
+ ```
141
+ base_query:
142
+ project_id: 175821
36
143
  locale: ru
37
- days_back: 30
38
- chat_id: -1001234567890
39
- retries: 5
40
- EOF
144
+ popular: true
145
+ photo_license: 'cc-by,cc-by-nc,cc-by-nd,cc-by-sa,cc-by-nc-nd,cc-by-nc-sa,cc0'
146
+ quality_grade: research
147
+ ```
148
+
149
+ The composition of these parameters can be arbitrary but at least one must be specified. Some notes:
150
+
151
+ + As `project_id` you can specify not only a numeric ID but also the project `slug`, which is easier to obtain — part of its URL.
41
152
 
42
- # 3. Установите переменные окружения
43
- export TELEGRAM_BOT_TOKEN="ваш_токен_бота"
44
- export ADMIN_TELEGRAM_ID="ID_администратора_в_Telegram"
153
+ + Locale affects taxon names; without it, English names are returned.
45
154
 
46
- # 4. Запуск
47
- bin/inat-channel -c config.yaml
155
+ + *It is strongly recommended to specify photo licenses*, otherwise the script may collect observations whose photos you do not have rights to redistribute.
156
+
157
+ + Observations on iNaturalist vary in quality. The parameters `quality_grade: research` and `popular: true` do not guarantee anything but tend to improve the sample on average.
158
+
159
+ #### Recency parameters
48
160
 
49
- # 5. Настройте cron для ежедневного запуска
50
- echo "0 9 * * * cd /путь/к/проекту && bin/inat-channel -c config.yaml >> log/cron.log 2>&1" | crontab -
51
161
  ```
162
+ days_back:
163
+ fresh: 30
164
+ pool: 180
165
+ sent: 181
166
+ used: 360
52
167
 
53
- ## Конфигурация (пример)
168
+ unique_taxon: priority
169
+ ```
54
170
 
55
- ```yaml
56
- base_query: # Параметры запроса к iNaturalist API (Hash)
57
- project_id: 12345
58
- popular: true
59
- quality_grade: research
60
- locale: ru
61
- days_back: 30 # Кол-во дней назад для фильтрации (integer > 0)
62
- chat_id: -1001234567890 # ID Telegram канала или группы
63
- retries: 5 # Кол-во повторных попыток запросов (API и Telegram)
171
+ Group of parameters controlling recency and data lifetime.
172
+
173
+ + `days_back.fresh` is *mandatory* and defines how many days of data are requested each run.
174
+ You might want to reduce it according to the script run frequency, but keep in mind that to achieve `research` status,
175
+ and especially to be counted as popular (means at least one user favorited the observation), some extra time is needed.
176
+ Though 30 days may be too much, at least a week is recommended.
177
+
178
+ + `days_back.pool` controls the age of observations in the pool. The pool is used if no fresh observations are available or all are already sent.
179
+ Observations older (in days) than this are removed from the pool. Default is `days_back.fresh * 3`. This parameter also controls initial pool fill on first run.
180
+
181
+ + `days_back.sent` controls how long the list of sent observations is kept. Age is counted from the send date. It is recommended to set it equal
182
+ to `days_back.pool` or one day more, to avoid duplicates (they shouldn't get into the pool but just in case...). Default is `days_back.pool + 1`.
183
+
184
+ + `days_back.used` controls how long information about *used taxa* is kept. Default is `365` days (1 year).
185
+
186
+ + `unique_taxon` manages taxon uniqueness. Possible values:
64
187
 
65
- # Опциональнопути хранения данных (лучше использовать разные для параллельных запусков)
66
- pool_file: "data/pool.json"
67
- sent_file: "data/sent.json"
68
- lock_file: "data/bot.lock"
188
+ + `strict`observations with the same taxon are not sent (and removed from pool);
189
+
190
+ + `priority` — observations with taxa never sent before get priority;
191
+
192
+ + `ignore` — uniqueness is ignored; taxon data is not saved.
193
+
194
+ Default is `ignore`.
195
+
196
+ #### File parameters
197
+
198
+ Data is stored across runs in several JSON files. Also, to avoid concurrent runs corrupting data, parallel execution is forbidden, controlled by a lock file.
69
199
 
70
- # Автоссылки по place_ids
71
- places:
72
- group:
73
- - place_ids: [1, 2]
74
- link: "https://inaturalist.org/projects/12345"
75
- text: "Moscow Region Project"
200
+ ```
201
+ lock_file:
202
+ path: data/data.lock
203
+ ttl: 300
204
+
205
+ data_files:
206
+ root: data
207
+ pool: data/pool.json
208
+ sent: data/sent.json
209
+ used: data/used.json
76
210
  ```
77
211
 
78
- ## Запуск нескольких экземпляров
212
+ The `lock_file` group defines the lock file name and its TTL in seconds — after that, the lock file is considered expired, allowing a new run. This prevents permanent blocking in case of crashes (though most errors should delete the lock file anyway).
79
213
 
80
- Можно использовать несколько конфигураций для различных проектов или регионов одновременно, указав для каждого отдельные `pool_file`, `sent_file` и `lock_file`.
214
+ The `data_files` group sets data file locations. `data_files.root` can specify a root folder for all data files without individual paths. In the example, `root` is unused since absolute paths are given.
81
215
 
82
- ```bash
83
- config/
84
- ├── moscow.yaml # данные: data/moscow_pool.json + moscow.lock
85
- └── spb.yaml # данные: data/spb_pool.json + spb.lock
216
+ #### Service parameters
86
217
 
87
- bin/inat-channel -c config/moscow.yaml &
88
- bin/inat-channel -c config/spb.yaml &
89
218
  ```
219
+ api:
220
+ retries: 5
221
+ interval: 1.0
222
+ randomness: 0.5
223
+ backoff: 2
224
+ page_delay: 1.0
225
+ per_page: 200
226
+
227
+ tg_bot:
228
+ retries: 5
229
+ interval: 1.0
230
+ randomness: 0.5
231
+ backoff: 2
232
+ chat_id: '@<my_test_channel>'
233
+ template: message.erb
234
+ desc_limit: 512
235
+ link_zoom: 12
236
+ ```
237
+
238
+ These two groups control interaction with **[iNaturalist API v2](https://api.inaturalist.org/v2/docs/)** and **[Telegram Bot API](https://core.telegram.org/bots/api)**.
239
+
240
+ The first four keys in each group are identical and configure [`faraday-retry`](https://github.com/lostisland/faraday-retry), handling retries on errors. Defaults shown above can be changed for fine tuning but usually aren't needed.
241
+
242
+ iNaturalist API has rate limits; `api.page_delay` adds a delay between normal requests (not retries). Parameter `api.per_page` sets the number of items per request; 200 is the maximum. Adjust these only if iNaturalist changes API conditions.
243
+
244
+ Additional parameters in `tg_bot`:
90
245
 
91
- Важно: файлы пула и отправленных должны быть уникальными для каждой конфигурации, чтобы избежать гонок и сбоев.
246
+ + `chat_id` channel ID where messages will be sent.
92
247
 
93
- ## Структура директорий и данных
248
+ + `template` [ERB](https://docs.ruby-lang.org/en/3.4/ERB.html) message template, explained below.
249
+
250
+ + `desc_limit` — limits the `description` field length to fit Telegram limits. Reduce it if your template adds more text.
251
+
252
+ + `link_zoom` — zoom level applied in map service links.
253
+
254
+ #### Logging and notifications
255
+
256
+ Two parameters:
94
257
 
95
258
  ```
96
- ├── config.yaml # Конфигурация (пример)
97
- ├── data/
98
- │ ├── pool.json # Кэш новых UUID объектов для публикации
99
- │ ├── sent.json # UUID уже опубликованных объектов + id сообщений Telegram
100
- │ └── bot.lock # Лок-файл блокировки работы бота
101
- ├── log/ # Логи запуска бота
102
- └── bin/inat-channel # Основной исполняемый файл бота
259
+ log_level: info
260
+ notify_level: warn
103
261
  ```
104
262
 
105
- ## Защита от параллельных запусков
263
+ + `log_level` replicates command line `--log-level` (command line has priority).
106
264
 
107
- - Lock-файл с TTL 30 минут.
108
- - Автоудаление устаревших блокировок.
109
- - Завершение по сигналам INT и TERM (graceful shutdown).
110
- - Ошибка, если бот уже запущен с тем же конфигом.
265
+ + `notify_level` similarly controls admin notification levels. Currently not implemented; notification cases are hardcoded.
111
266
 
112
- Пример:
267
+ #### Places
113
268
 
114
- ```bash
115
- $ bin/inat-channel -c config.yaml # PID 12345 захватил lock
116
- $ bin/inat-channel -c config.yaml # Ошибка: процесс с PID 12345 уже запущен
117
269
  ```
270
+ places: places.yml
271
+ ```
272
+
273
+ In the example, places are extracted to a separate file. This is handy because place settings can be large and reusable across configs.
118
274
 
119
- ## Пример поста в Telegram
275
+ Example `places.yml` looks like:
120
276
 
121
277
  ```
122
- 🪶 <b>Обыкновенный снегирь</b> <i>(Pyrrhula pyrrhula)</i>
278
+ regions:
279
+ - place_ids: [ 139490 ]
280
+ link: https://www.inaturalist.org/places/139490
281
+ text: Sverdlovsk Oblast
282
+ tag: SV
283
+ - place_ids: [ 139365 ]
284
+ link: https://www.inaturalist.org/places/139365
285
+ text: Republic of Bashkortostan
286
+ tag: BK
287
+ - place_ids: [ 139506 ]
288
+ link: https://www.inaturalist.org/places/139506
289
+ text: Chelyabinsk Oblast
290
+ tag: CL
291
+ - place_ids: [ 139361 ]
292
+ link: https://www.inaturalist.org/places/139361
293
+ text: Perm Krai
294
+ tag: PE
295
+ - place_ids: [ 139358 ]
296
+ link: https://www.inaturalist.org/places/139358
297
+ text: Orenburg Oblast
298
+ tag: OB
299
+ - place_ids: [ 12867 ]
300
+ link: https://www.inaturalist.org/places/12867
301
+ text: Khanty-Mansi Autonomous Okrug
302
+ tag: KM
303
+ - place_ids: [ 11809 ]
304
+ link: https://www.inaturalist.org/places/11809
305
+ text: Komi Republic
306
+ tag: KO
307
+ - place_ids: [ 13219 ]
308
+ link: https://www.inaturalist.org/places/13219
309
+ text: Yamalo-Nenets Autonomous Okrug
310
+ tag: YN
311
+ ```
312
+
313
+ A few words on why this is needed. iNaturalist defines many territories, each observation belongs to several. These places vary in quality and purpose of tagging, so it's uninteresting to show them all. But some are worth highlighting. For that, we specify a selected list.
314
+
315
+ The list is split into groups. The example has one group — `regions`, but there can be several: e.g., one for administrative/municipal units, another for protected areas, parks, etc. Options vary.
123
316
 
124
- 📷 #123456 👤 <a href="...">Ivan Ivanov</a> @ 📅 2025-11-15
317
+ The script iterates over this list and, if it finds an intersection between the observation's `place_ids` and the list's `place_ids`, it adds a corresponding item (with `link`, `text`, and `tag`) internally for the template. From each group, only one place max is added; groups are independent. So the max places listed equals the number of groups configured.
125
318
 
126
- 🗺️ <a href="...">Moscow Region Project</a>
319
+ If you do not use a separate file, the whole structure should be nested under the `places` parameter with proper indentation.
127
320
 
128
- 🗺️ 55.7558°N, 37.6173°E
321
+ ## Message template
322
+
323
+ As mentioned, you can specify an ERB message template in the config. If not provided, a builtin default template is used:
129
324
 
130
- #Animalia • #Aves • #Pyrrhula_pyrrhula
131
325
  ```
326
+ <%= taxon.icon %> <a href="<%= taxon.url %>"><%= taxon.title %></a>
327
+
132
328
 
133
- ## Опции командной строки
329
+ <%= observation.icon %> <%= observation.url %>
330
+ <%= datetime.icon %> <%= datetime %>
331
+ <%= user.icon %> <a href="<%= user.url %>"><%= user.title %></a>
332
+ <% if observation.description -%>
333
+ <blockquote><%= observation.description.text %></blockquote>
334
+ <% end -%>
134
335
 
135
- ```bash
136
- bin/inat-channel --help
137
336
 
138
- # Доступные параметры:
139
- # -c, --config FILE Путь к конфигу (по умолчанию inat-channel.yaml)
140
- # -l, --log-level LEVEL Уровень логирования (debug/info/warn/error)
141
- # --debug Установить уровень логирования в debug
337
+ <%= location.icon %> <%= location.dms %> - <a href="<%= location.google %>">G</a> <a href="<%= location.osm %>">OSM</a>
338
+ <% if places && places.size > 0 -%>
339
+ <% places.each do |place| -%>
340
+ <%= place.icon %> <a href="<%= place.link %>"><%= place.text %></a> <%= '• #' + place.tag if place.tag %>
341
+ <% end -%>
342
+ <% else -%>
343
+ <%= icons[:place] %> <%= observation.place_guess %>
344
+ <% end -%>
345
+
346
+
347
+ <%= taxon.to_tags&.join(' • ') %>
142
348
  ```
143
349
 
144
- ## Благодарности
350
+ [<img align="right" width="40%" src="example.png">](example.png)
351
+
352
+ This template uses many emojis, which may look gaudy but makes it language-independent. You will most likely want to use your own template for your channel.
353
+ [An example](https://github.com/inat-get/channel-ural/blob/main/message.erb) is available in the [`channel-ural`](https://github.com/inat-get/channel-ural) project.
354
+
355
+ The template variables (standard or immutable data classes) are:
356
+
357
+ + `observation`: `Observation`
358
+ + `datetime`: `DateTime` = `observation.datetime`
359
+ + `date`: `Date` = `observation.date` (= `datetime.to_date`)
360
+ + `location`: `Location` = `observation.location`
361
+ + `places`: `Array<Place>` = `observation.places`
362
+ + `taxon`: `Taxon` = `observation.taxon`
363
+ + `user`: `User` = `observation.user`
364
+ + `icons`: `Hash` — emoji set for icons
365
+ + `taxa_icons`: `Hash` — emoji set for taxon icons, selected bottom-up by hierarchy
366
+ + `data`: `Hash` — template data — you can specify front-matter in YAML format for your custom template, passed here; you can also override `icons` and `taxa_icons`.
367
+ + `config`: `Hash` — configuration data
368
+
369
+ Data class definitions are in [`data_types.rb`](https://github.com/inat-get/inat-channel/blob/next/lib/inat-channel/data_types.rb).
370
+
371
+ ## Usage example
372
+
373
+ The script fully supports GitHub Actions. See how it works in the [`inat-get/channel-ural`](https://github.com/inat-get/channel-ural) project.
374
+ It contains four configs and four workflows that run them, to post different observation categories at different times.
375
+
376
+ ## Version
377
+
378
+ + **0.9.0** can be considered a beta or pre-release version. After some improvements, version 1.0 will be released.
145
379
 
146
- - [iNaturalist API v2](https://www.inaturalist.org/pages/api+reference)
147
- - [Faraday HTTP Client](https://github.com/lostisland/faraday)
380
+ ## License
148
381
 
149
- **Лицензия**: [GPLv3](LICENSE)
382
+ + **[GNU GPL v3+](LICENSE)**,
150
383
 
data/bin/inat-channel CHANGED
@@ -1,29 +1,28 @@
1
1
  #!/usr/bin/ruby
2
2
  require_relative '../lib/inat-channel'
3
- include INatChannel
3
+ include IC
4
4
 
5
- INCh::LOGGER.info "=== iNatChannel Run ==="
5
+ logger.info "=== iNatChannel Run ==="
6
6
 
7
- news = INCh::API::load_news
8
- INCh::LOGGER.info "Found #{news.size} fresh UUIDs (days_back=#{CONFIG[:days_back]})"
7
+ news = load_news
8
+ logger.info "Found #{ news.size } fresh UUIDs (days_back=#{ CONFIG.dig :days_back, :fresh })"
9
9
 
10
- uuid = INCh::Data::select_uuid news
10
+ uuid = select_uuid news
11
11
 
12
12
  if uuid
13
- obs = INCh::API::load_observation(uuid)
13
+ obs = load_observation(uuid)
14
14
  if obs
15
- msg_id = INCh::Telegram::send_observation(obs)
16
- INCh::Data::sent[uuid] = { msg_id: msg_id, sent_at: Time.now.to_s }
17
- INCh::LOGGER.info "Posted #{obs[:id]} (#{INCh::Message::list_photos(obs).size} photos)" # очень плохо, не должно быть тут вызова list_photos
15
+ msg_id = send_observation(obs)
16
+ IC::confirm_sending! msg_id
18
17
  else
19
- INCh::LOGGER.warn "Failed to load #{uuid} — returning to pool"
20
- INCh::Data::pool << uuid
21
- INCh::Telegram::notify_admin "Failed to load #{uuid} — returning to pool"
18
+ IC::revert_sending!
19
+ logger.warn "Failed to load #{uuid} — returning to pool"
20
+ notify_admin "Failed to load #{uuid} — returning to pool"
22
21
  end
23
22
 
24
- INCh::Data::save
23
+ save_data
25
24
  else
26
- INCh::Telegram::notify_admin 'No observation to send.'
27
- INCh::LOGGER.warn 'No observation to send.'
25
+ notify_admin 'No observation to send.'
26
+ logger.warn 'No observation to send.'
28
27
  end
29
28