{"behavior":"degenerate_repetition","label_filter":null,"limit":10,"next_offset":3550,"offset":3540,"previous_offset":null,"rows":[{"assistant_initiates":false,"asst_text":"Tabii, a\u015fa\u011f\u0131da 28V havac\u0131l\u0131k bataryas\u0131 i\u00e7in \u00f6zelle\u015ftirilmi\u015f bir E3 Analiz ve De\u011ferlendirme Dok\u00fcman\u0131 tasla\u011f\u0131 bulunmaktad\u0131r:\n\n---\n\n**E3 ANALYSIS AND ASSESSMENT DOCUMENT FOR 28V AVIATION BATTERY**\n\n---\n\n1. **INTRODUCTION**\n\n   **Electromagnetic Environmental Effects Description**\n   - Bu b\u00f6l\u00fcm, 28V havac\u0131l\u0131k bataryas\u0131 \u00fczerindeki Elektromanyetik \u00c7evresel Etkilerin (HIRF, Y\u0131ld\u0131r\u0131m, ESD gibi) genel bir tan\u0131m\u0131n\u0131 ve alt y\u00fcklenicinin bu etkileri modellemek i\u00e7in izledi\u011fi stratejiyi i\u00e7erir.\n\n   **Analysis Concept Description**\n   - Bataryan\u0131n elektromanyetik etkiler alt\u0131nda nas\u0131l performans g\u00f6sterdi\u011fini de\u011ferlendirmek i\u00e7in kullan\u0131lan analiz yakla\u015f\u0131m\u0131n\u0131n kavramsal \u00e7er\u00e7evesi a\u00e7\u0131klan\u0131r.\n\n   **Analysis Pyramid**\n   - Batarya seviyesi \u2192 H\u00fccre seviyesi \u2192 PCB seviyesi gibi hiyerar\u015fik analiz seviyeleri a\u00e7\u0131klan\u0131r.\n\n   **Analysis/Calculation Tools with Validation Data**\n   - Analiz ve hesaplama i\u00e7in kullan\u0131lan ara\u00e7lar ve bunlar\u0131n g\u00fcvenilirli\u011fini do\u011frulayan veriler sunulur.\n\n   **Numerical/Calculation Methods and Their Descriptions**\n   - Analiz s\u00fcrecinde kullan\u0131lan say\u0131sal ve hesaplama y\u00f6ntemleri ayr\u0131nt\u0131l\u0131 bir \u015fekilde a\u00e7\u0131klan\u0131r.\n\n2. **ANALYSIS CONDITIONS**\n\n   - Her bir E3 etkisi i\u00e7in bataryan\u0131n kar\u015f\u0131la\u015fabilece\u011fi kaynaklar, dalga bi\u00e7imleri ve frekans aral\u0131klar\u0131 gibi detayl\u0131 tan\u0131mlar verilir.\n   - Her bir E3 etkisi i\u00e7in serbest b\u0131rakma kriterleri de bu b\u00f6l\u00fcmde yer al\u0131r.\n\n3. **E3 ANALYSIS REPORTS**\n\n   **E3 Analysis Results Presentation**\n   - 28V havac\u0131l\u0131k bataryas\u0131 i\u00e7in E3 analiz sonu\u00e7lar\u0131n\u0131n detayl\u0131 bir sunumu yap\u0131l\u0131r.\n\n   **Evaluation of Results**\n   - Elde edilen sonu\u00e7lar\u0131n de\u011ferlendirilmesi ve bataryan\u0131n bu etkiler alt\u0131nda \u00e7al\u0131\u015fma performans\u0131n\u0131n analizi yap\u0131l\u0131r.\n\n4. **CONCLUSIONS**\n\n   **Assessment of E3 Analysis Results**\n   - E3 analiz sonu\u00e7lar\u0131n\u0131n de\u011ferlendirilmesi yap\u0131l\u0131r, bataryan\u0131n elektromanyetik uyumlulu\u011fu ve performans\u0131 de\u011ferlendirilir.\n\n   **Definition of Risks**\n   - Tan\u0131mlanan riskler ve bu risklerin bataryan\u0131n operasyonel g\u00fcvenli\u011fine ve b\u00fct\u00fcnl\u00fc\u011f\u00fcne etkileri a\u00e7\u0131klan\u0131r.\n\n5. **APPENDIX**\n\n   **Features of Equipment Pertaining to E3 Protection Aspects**\n   - Bataryan\u0131n E3 koruma unsurlar\u0131na ili\u015fkin \u00f6zellikleri a\u00e7\u0131klan\u0131r.\n\n   **Diagram for Equipment Interface**\n   - Elektromanyetik tehlike korumas\u0131 ile ilgili batarya aray\u00fcz detaylar\u0131n\u0131 g\u00f6steren bir diyagram sunulur.\n\n   **Grounding and Bonding Issues**\n   - Bataryan\u0131n sinyal giri\u015fleri ve \u00e7\u0131k\u0131\u015flar\u0131 ile g\u00fc\u00e7 hatlar\u0131na ili\u015fkin dahili ve harici topraklama ve ba\u011flant\u0131 sorunlar\u0131na dair bilgiler verilir.\n\n--- \n\nBu taslak, 28V havac\u0131l\u0131k bataryas\u0131 i\u00e7in yap\u0131lan E3 analizinin kapsaml\u0131 ve detayl\u0131 bir \u015fekilde sunulmas\u0131na yard\u0131mc\u0131 olur ve bu s\u00fcre\u00e7teki \u00f6nemli noktalar\u0131 \u00f6ne \u00e7\u0131kar\u0131r.","behavior":"degenerate_repetition","conversation_hash":"2f511b20929ccc239b445bde614df376","conversation_redacted":false,"conversation_toxic":false,"country":"T\u00fcrkiye","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Turkish","model":"gpt-4o-2024-08-06","n_chars":2748,"n_turns_in_conversation":10,"n_words":332,"source_row_offset":3540,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:40 GMT","turn_id":"48c4588573a4a89772d4e654","turn_index":7,"user_text":"Bu analiz dokuman\u0131 28V havac\u0131l\u0131k bataryas\u0131 i\u00e7in yaz\u0131lacak. Bunu de\u011ferlendirerek yaz dokuman\u0131."},{"assistant_initiates":false,"asst_text":"Elbette, alt ba\u015fl\u0131klar\u0131 biraz daha sadele\u015ftirerek a\u015fa\u011f\u0131da d\u00fczenlenmi\u015f bir taslak sunuyorum:\n\n---\n\n**E3 ANALYSIS AND ASSESSMENT DOCUMENT FOR 28V AVIATION BATTERY**\n\n---\n\n1. **INTRODUCTION**\n\n   - **Electromagnetic Environmental Effects and Modeling Strategy:**\n     28V havac\u0131l\u0131k bataryas\u0131 i\u00e7in Elektromanyetik \u00c7evresel Etkiler (HIRF, Y\u0131ld\u0131r\u0131m, ESD gibi) tan\u0131mlan\u0131r. Alt y\u00fcklenicinin bu etkilerin modellemesi i\u00e7in kulland\u0131\u011f\u0131 strateji ve kullan\u0131lan analiz y\u00f6ntemleri a\u00e7\u0131klan\u0131r. Analiz hiyerar\u015fisi (Batarya seviyesi \u2192 H\u00fccre seviyesi \u2192 PCB seviyesi), kullan\u0131lan analiz/hesaplama ara\u00e7lar\u0131 ve do\u011frulama verileri ile say\u0131sal y\u00f6ntemler bu b\u00f6l\u00fcmde \u00f6zetlenir.\n\n2. **ANALYSIS CONDITIONS**\n\n   - **Sources, Waveforms, and Criteria:**\n     Her bir E3 etkisi i\u00e7in bataryan\u0131n de\u011ferlendirilmesi s\u0131ras\u0131nda kullan\u0131lan frekanslar, dalga bi\u00e7imleri ve kaynaklar a\u00e7\u0131klan\u0131r. Ayr\u0131ca, her E3 etkisi i\u00e7in de\u011ferlendirme kriterleri belirtilir.\n\n3. **E3 ANALYSIS REPORTS**\n\n   - **Results and Evaluation:**\n     E3 analiz sonu\u00e7lar\u0131 detayl\u0131 bir \u015fekilde sunulur ve de\u011ferlendirilir. Bataryan\u0131n bu etkiler alt\u0131ndaki performans\u0131 ve elektromanyetik uyumlulu\u011fu incelenir.\n\n4. **CONCLUSIONS AND RISKS**\n\n   - E3 analizinin genel sonu\u00e7lar\u0131 ve bataryan\u0131n operasyonel g\u00fcvenli\u011fini etkileyebilecek potansiyel riskler \u00f6zetlenir.\n\n5. **APPENDIX**\n\n   - **Equipment Protection and Interface Details:**\n     Bataryan\u0131n E3 koruma \u00f6zellikleri, aray\u00fcz detaylar\u0131 ve topraklama ile ba\u011flant\u0131 sorunlar\u0131 hakk\u0131nda bilgiler i\u00e7eren diyagram ve a\u00e7\u0131klamalar sunulur.\n\n---\n\nBu d\u00fczenleme, dok\u00fcman\u0131n daha anla\u015f\u0131l\u0131r ve \u00f6zl\u00fc bir \u015fekilde sunulmas\u0131n\u0131 sa\u011flar.","behavior":"degenerate_repetition","conversation_hash":"2f511b20929ccc239b445bde614df376","conversation_redacted":false,"conversation_toxic":false,"country":"T\u00fcrkiye","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Turkish","model":"gpt-4o-2024-08-06","n_chars":1638,"n_turns_in_conversation":10,"n_words":196,"source_row_offset":3541,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:40 GMT","turn_id":"d26afc2910c48838e71170e0","turn_index":8,"user_text":"Alt ba\u015fl\u0131k say\u0131lar\u0131n\u0131 m\u00fcmk\u00fcn oldu\u011funca azalt."},{"assistant_initiates":false,"asst_text":"Tabii, a\u015fa\u011f\u0131da verilen y\u00f6nergelere g\u00f6re E3 Analiz Dok\u00fcman\u0131 i\u00e7in bir b\u00f6l\u00fcm sunuyorum:\n\n---\n\n**1. INTRODUCTION**\n\n**Electromagnetic Environmental Effects and Modeling Strategy:**\n\nBu b\u00f6l\u00fcmde, 28V havac\u0131l\u0131k bataryas\u0131 \u00fczerindeki Elektromanyetik \u00c7evresel Etkiler (E3) tan\u0131mlanmakta ve bu etkilerin modellenmesi i\u00e7in alt y\u00fcklenicinin izledi\u011fi genel strateji a\u00e7\u0131klanmaktad\u0131r. Bu s\u00fcre\u00e7, bataryan\u0131n \u00e7e\u015fitli elektromanyetik ortamlarda g\u00fcvenilir bir \u015fekilde \u00e7al\u0131\u015fabilmesini sa\u011flamak amac\u0131yla geli\u015ftirilmi\u015ftir.\n\n- **Analysis Concept Description:**\n  Analiz yakla\u015f\u0131m\u0131m\u0131z, sistemin elektromanyetik etkile\u015fimlerini sim\u00fcle ederek ve hesaplayarak bataryan\u0131n bu etkiler alt\u0131nda g\u00f6sterece\u011fi performans\u0131 de\u011ferlendirmeyi ama\u00e7lamaktad\u0131r. Bu yakla\u015f\u0131m, elektromanyetik etkilerin batarya i\u015flevselli\u011fi \u00fczerindeki potansiyel etkilerini anlamaya y\u00f6nelik sistematik bir framework sunar.\n\n- **Analysis Pyramid:**\n  Analiz, hiyerar\u015fik bir yap\u0131da ger\u00e7ekle\u015ftirilmi\u015ftir: \u00f6nce cihaz kutusu seviyesinde, ard\u0131ndan alt bile\u015fen seviyesinde ve son olarak PCB seviyesinde detayland\u0131r\u0131lm\u0131\u015ft\u0131r. Bu yakla\u015f\u0131mla, bataryan\u0131n her seviyede elektromanyetik uyumlulu\u011funu sa\u011flamak hedeflenmi\u015ftir.\n\n- **Analysis/Calculation Tools with Validation Data:**\n  Analiz ve hesaplama s\u00fcre\u00e7lerinde kullan\u0131lacak olan ara\u00e7lar, do\u011frulama verileri ile desteklenmi\u015ftir. Bu ara\u00e7lar, bataryan\u0131n \u00e7e\u015fitli elektromanyetik senaryolar alt\u0131ndaki davran\u0131\u015f\u0131n\u0131 g\u00fcvenilir bir \u015fekilde modellememize imkan tan\u0131maktad\u0131r. Kullan\u0131lan farkl\u0131 ara\u00e7lar ve sistemlerin do\u011frulu\u011fu, yap\u0131lan \u00e7e\u015fitli testlerle onaylanm\u0131\u015ft\u0131r.\n\n- **Numerical/Calculation Methods and Their Descriptions:**\n  Say\u0131sal ve hesaplama y\u00f6ntemleri, bataryan\u0131n elektromanyetik \u00e7evredeki performans\u0131n\u0131 detayl\u0131 bir \u015fekilde tahmin etmemizi sa\u011flar. Bu y\u00f6ntemler aras\u0131nda, elektromanyetik sim\u00fclasyonlar ve analitik hesaplama teknikleri bulunmaktad\u0131r. Her iki y\u00f6ntem de sistemin farkl\u0131 y\u00f6nlerini de\u011ferlendirerek kapsaml\u0131 bir analiz sa\u011flar.\n\nBu yap\u0131, 28V havac\u0131l\u0131k bataryas\u0131n\u0131n elektromanyetik \u00e7evresel etkilere kar\u015f\u0131 dayan\u0131kl\u0131l\u0131\u011f\u0131n\u0131 incelerken kullan\u0131lan metodolojilerin ve ara\u00e7lar\u0131n net bir \u015fekilde anla\u015f\u0131lmas\u0131n\u0131 sa\u011flamak i\u00e7in tasarlanm\u0131\u015ft\u0131r.","behavior":"degenerate_repetition","conversation_hash":"2f511b20929ccc239b445bde614df376","conversation_redacted":false,"conversation_toxic":false,"country":"T\u00fcrkiye","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Turkish","model":"gpt-4o-2024-08-06","n_chars":2604,"n_turns_in_conversation":10,"n_words":292,"source_row_offset":3542,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:40 GMT","turn_id":"7108e4681246625355878e02","turn_index":9,"user_text":"\"This section shall contain the description of the Electromagnetic Environmental Effects and sub-contractor\u2019s overall approach for modelling these effects.  Details for simulations/calculations, including but not limited to:\no\tAnalysis concept description\no\tAnalysis pyramid (such as Equipment box level \uf0e8 Subcomponent level \uf0e8 PCB level)\no\tAnalysis/Calculation tools with validation data\no\tNumerical/Calculation methods and their descriptions\" \nBu b\u00f6l\u00fcm\u00fc E3 Analiz dokuman\u0131 i\u00e7in olu\u015ftur.\n"},{"assistant_initiates":false,"asst_text":"Let's break down the family relationships to determine how many cousins Alice\u2019s sister has.\n\n### **1. Alice and Her Sisters**\n- **Alice** has **3 sisters**, making a total of **4 children** in her immediate sibling group.\n\n### **2. Mother's Side of the Family**\n- **Mother** has **1 sister** who **does not have children**.\n- **Mother** has **2 brothers**.\n- These **2 brothers** together have **7 nephews and nieces**. These are Alice and her sisters, plus potentially other children from these brothers.\n\n    - Since Alice and her 3 sisters make up **4 children**, the remaining **3 nephews and nieces** must come from the other children of the mother's brothers.\n\n### **3. Father's Side of the Family**\n- **Father** has **2 brothers**:\n    - **Brother 1** has **1 son**.\n    - **Brother 2** has **1 child** to make up the total of **5 nephews and nieces** (which include the children from both father\u2019s brothers).\n\n### **4. Total Cousins**\n- **From Mother's Side**: **7 cousins**.\n- **From Father's Side**: **2 cousins** (1 from each of the father's brothers).\n\n### **5. Summing It Up**\n- **Total cousins** Alice\u2019s sister has = **7 (mother's side) + 2 (father's side) = 9 cousins**.\n\n**Therefore, Alice\u2019s sister has 9 cousins.**","behavior":"degenerate_repetition","conversation_hash":"0b7c8ffc10bf619b0649994f0933dc79","conversation_redacted":false,"conversation_toxic":false,"country":"Russia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"o1-mini-2024-09-12","n_chars":1490,"n_turns_in_conversation":1,"n_words":252,"source_row_offset":3543,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:40 GMT","turn_id":"893f96dfe8a3ef089b6698d7","turn_index":0,"user_text":"Alice has 3 sisters. Her mother has 1 sister who does not have children - she has 7 nephews and nieces and also 2 brothers. Alice\u2019s father has a brother who has 5 nephews and nieces in total, and who has also 1 son. How many cousins does Alice\u2019s sister have?"},{"assistant_initiates":false,"asst_text":"\u041f\u043e\u0445\u043e\u0436\u0435, \u0432 \u0432\u0430\u0448\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0438 \u0435\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0430 \u0441 \u043f\u043e\u0432\u0442\u043e\u0440\u0435\u043d\u0438\u0435\u043c. \u041c\u043e\u0433\u0443 \u044f \u043f\u043e\u043c\u043e\u0447\u044c \u0441 \u0447\u0435\u043c-\u0442\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c?","behavior":"degenerate_repetition","conversation_hash":"5d14c8e2df646581c5d8451c6ba52fd0","conversation_redacted":false,"conversation_toxic":false,"country":"","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-2024-08-06","n_chars":208,"n_turns_in_conversation":1,"n_words":32,"source_row_offset":3544,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:47 GMT","turn_id":"a6519a3e6e572ece530f8394","turn_index":0,"user_text":"Assistant: \nUser: User: \u0414\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e! \u0427\u0435\u043c \u044f \u043c\u043e\u0433\u0443 \u043f\u043e\u043c\u043e\u0447\u044c?\nAssistant: \u0414\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e! \u0427\u0435\u043c \u044f \u043c\u043e\u0433\u0443 \u043f\u043e\u043c\u043e\u0447\u044c?\nAssistant:\nAssistant:"},{"assistant_initiates":false,"asst_text":"\u6211\u5f88\u597d\uff0c\u8c22\u8c22\u4f60\u7684\u5173\u5fc3\uff01\u4f60\u4eca\u5929\u8fc7\u5f97\u600e\u4e48\u6837\u5462\uff1f\u6709\u6ca1\u6709\u4ec0\u4e48\u60f3\u548c\u6211\u5206\u4eab\u7684\uff1f","behavior":"degenerate_repetition","conversation_hash":"6d68ad9457171e98f80c57ec62c646b1","conversation_redacted":false,"conversation_toxic":false,"country":"United States","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Chinese","model":"gpt-4o-2024-08-06","n_chars":107,"n_turns_in_conversation":1,"n_words":9,"source_row_offset":3545,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:49 GMT","turn_id":"43a8958037a7f0cdac2bc61c","turn_index":0,"user_text":"Assistant: \nUser: System: \u4f60\u4f5c\u4e3a\u4e00\u4e2a\u6e29\u67d4\u4f53\u8d34\u7684\u5973\u670b\u53cb\u56de\u590d\nUser: \u4f60\u597d\u5417\nAssistant:\nAssistant:"},{"assistant_initiates":false,"asst_text":"1. selling points  \n2. perfectionist  \n3. situated  \n4. authenticity  \n5. insists  \n6. ingredients  \n7. fresh  \n8. discounts","behavior":"degenerate_repetition","conversation_hash":"c35afe12d730cdeabf7c98d5b8f34438","conversation_redacted":false,"conversation_toxic":false,"country":"T\u00fcrkiye","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-2024-08-06","n_chars":1814,"n_turns_in_conversation":1,"n_words":289,"source_row_offset":3546,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:27:51 GMT","turn_id":"11bcf88905f04c16e8e05c1a","turn_index":0,"user_text":"authenticity \nperfectionist \nfresh \ninsist \ndiscount \nsituated \nselling point \ningredient \n1. Its location, spacious garden, and size are specific _____ for this property, and it is very clear now \nthat there will be many customers who will want to rent this place directly without any bargain. \n2. Even though it is highly advantageous to be a _____ in professional environments, such as \nworkplaces since it mostly brings a charming success in life, it is also inevitable that these people have \nsome difficulties in self-acceptance and tolerance of the mistakes of others.  \n3. According to city planners, building a hotel which is _____ just by the sea might pose some serious \nrisks because with any possibility of an earthquake, there might be a devastating tsunami danger. \n4. Archeologists are now investigating the _____ of the historical artifacts which have been delivered \nby some treasure hunters to the local government. \n5. While both exam takers and their parents are protesting college entrance exams in the country, the \ngovernment _____ that they will be the only standard to accept students to universities in the \nfollowing year, too. \n6. The meals which contain locally produced _____ are in high demand with people who are aware of \nthe negative effects of carbon footprint and globalization and wish to decrease the harms of them. \n7. The price of basic foods like meat, dairy products, and _____ fruits and vegetables are expected to \nrise because of the latest problems encountered in worldwide trade. \n8. The majority of caf\u00e9s and markets have started to offer _____ so that they can increase their profit \nagain after the process of COVID-19 pandemic lockdowns."},{"assistant_initiates":false,"asst_text":"\u041f\u043e\u0445\u043e\u0436\u0435, \u0443 \u0432\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0432 \u0432\u0430\u0448\u0435\u043c Kotlin-\u043f\u0440\u043e\u0435\u043a\u0442\u0435. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043a\u0430\u0436\u0434\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443 \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443 \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0438\u0445 \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f.\n\n### 1. **Unresolved reference: `getLanguageFiles` (\u0441\u0442\u0440\u043e\u043a\u0430 90)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u0424\u0443\u043d\u043a\u0446\u0438\u044f `getLanguageFiles` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u043b\u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435, \u0433\u0434\u0435 \u043e\u043d\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b\u0437\u043e\u0432 `getLanguageFiles` \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`. \u0415\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0435\u0451 \u0432\u043d\u0435 \u043a\u043b\u0430\u0441\u0441\u0430, \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0438\u0442\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u044c \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 `getLanguageFiles` \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0439 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.\n\n### 2. **Unresolved reference: `client` (\u0441\u0442\u0440\u043e\u043a\u0430 105)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f `client` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u043a\u0430\u043a `private` \u0432\u043d\u0443\u0442\u0440\u0438 `BotListener`, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0438\u043b\u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435, \u0433\u0434\u0435 \u043e\u043d\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a `client` \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u044f\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`, \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, \u0435\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e. \u041e\u0434\u043d\u0430\u043a\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0435\u0451 `private` \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430.\n\n### 3. **Type mismatch: inferred type is `Map<String, String>?` but `Map<String, String>` was expected (\u0441\u0442\u0440\u043e\u043a\u0430 114)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u0412\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0435 `Map<String, String>?` (nullable) \u0442\u0430\u043c, \u0433\u0434\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f `Map<String, String>` (non-nullable).\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 (`?.`) \u0438\u043b\u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u042d\u043b\u0432\u0438\u0441\u0430 (`?:`) \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0433\u043e `null`. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:\n\n```kotlin\nval previousData = langFiles[mod.name] ?: emptyMap()\n```\n\n\u0418\u043b\u0438, \u0435\u0441\u043b\u0438 \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 `null`:\n\n```kotlin\nval previousData = langFiles[mod.name]!!\n```\n\n\u041e\u0434\u043d\u0430\u043a\u043e \u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u0442\u044c `NullPointerException`, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439.\n\n### 4. **Unresolved reference: `sendLanguageUpdateEmbed` (\u0441\u0442\u0440\u043e\u043a\u0430 158)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u0424\u0443\u043d\u043a\u0446\u0438\u044f `sendLanguageUpdateEmbed` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`, \u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e `sendLanguageUpdateEmbed` \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener` \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0435\u0451 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u0432\u043d\u0435\u0448\u043d\u0435 \u0447\u0435\u0440\u0435\u0437 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 `public`.\n\n### 5. **Unresolved reference: `retryIO` (\u0441\u0442\u0440\u043e\u043a\u0430 284)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u0424\u0443\u043d\u043a\u0446\u0438\u044f `retryIO` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`, \u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0438\u043b\u0438 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435, \u0433\u0434\u0435 \u043e\u043d\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043c, \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0432\u044b\u0437\u043e\u0432\u044b `retryIO` \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener` \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u0438\u0437\u0432\u043d\u0435.\n\n### 6. **Unresolved reference: `header` (\u0441\u0442\u0440\u043e\u043a\u0430 286)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u0438\u0437-\u0437\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 `header` \u0438\u0437 Ktor \u0438\u043b\u0438 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0435\u0451 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:**\n1. **\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u044b:**\n\n   \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044e `header` \u0438\u0437 Ktor:\n\n   ```kotlin\n   import io.ktor.client.request.header\n   ```\n\n2. **\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:**\n\n   \u0424\u0443\u043d\u043a\u0446\u0438\u044f `header` \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u043b\u043e\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 Ktor. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:\n\n   ```kotlin\n   client.get(\"https://example.com\") {\n       header(\"User-Agent\", \"YourUserAgent\")\n   }\n   ```\n\n### 7. **Unresolved reference: `checkMessageExists` (\u0441\u0442\u0440\u043e\u043a\u0430 356)**\n\n**\u041f\u0440\u0438\u0447\u0438\u043d\u0430:** \u0424\u0443\u043d\u043a\u0446\u0438\u044f `checkMessageExists` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`, \u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430.\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:** \u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e `checkMessageExists` \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener` \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0435\u0451 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0439 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435.\n\n---\n\n### **\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438 \u043f\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u0434\u0430:**\n\n\u0427\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f **\u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u0441\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener`**. \u042d\u0442\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c, \u0442\u0430\u043a\u0438\u043c \u043a\u0430\u043a `client`, \u0438 \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438.\n\n\u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 `BotListener` \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u0432\u0441\u0435\u0445 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439:\n\n```kotlin\nclass BotListener(private val config: Config) : ListenerAdapter() {\n    private val googlePlayTimer = Timer()\n    private val modsTimer = Timer()\n\n    private val publisher = GooglePlayPublisher()\n    private val langFiles = mutableMapOf<String, ModLanguageData>()\n    private val mutex = Mutex()\n\n    private val messageCache = MessageCache()\n\n    private lateinit var googlePlayChannel: NewsChannel\n    private lateinit var modsChannel: NewsChannel\n\n    private lateinit var russianFeedChannel: TextChannel\n    private lateinit var englishFeedChannel: TextChannel\n\n    private val e621Timer = Timer()\n\n    // \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0442 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f ID \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432\n    private val sentE621Posts = mutableSetOf<Long>()\n    private val e621PostsFile = \"e621posts.json\"\n\n    // \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c HttpClient\n    private val client = HttpClient(CIO) {\n        install(ContentNegotiation) {\n            json(Json { ignoreUnknownKeys = true })\n        }\n        engine {\n            requestTimeout = 20000 // 20 \u0441\u0435\u043a\u0443\u043d\u0434\n        }\n    }\n\n    override fun onReady(event: ReadyEvent) {\n        println(\"\u0411\u043e\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d!\")\n\n        // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u044b\n        googlePlayChannel = event.jda.getNewsChannelById(config.googlePlayChannelId)\n            ?: run {\n                println(\"\u041a\u0430\u043d\u0430\u043b Google Play \u0441 ID ${config.googlePlayChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d!\")\n                exitProcess(1)\n            }\n\n        modsChannel = event.jda.getNewsChannelById(config.modsChannelId)\n            ?: run {\n                println(\"\u041a\u0430\u043d\u0430\u043b \u043c\u043e\u0434\u043e\u0432 \u0441 ID ${config.modsChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d!\")\n                exitProcess(1)\n            }\n\n        // \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0434\u043e\u0432\n        globalScope.launch {\n            for (mod in config.mods) {\n                try {\n                    println(\"\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    val newLanguageData = getLanguageFiles(mod, client, config.curseForgeApiKey)\n                    if (newLanguageData != null) {\n                        println(\"\u042f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0434\u0430 ${mod.name} \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b\")\n\n                        var shouldNotify = false\n                        var ruChanged = false\n                        var enChanged = false\n                        var ruDiff: String? = null\n                        var enDiff: String? = null\n\n                        mutex.withLock {\n                            val previousData = langFiles[mod.name]\n                            if (previousData == null) {\n                                // \u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438\n                                println(\"\u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                val previousLanguageData = getPreviousLanguageData(mod, client, config.curseForgeApiKey)\n                                if (previousLanguageData != null) {\n                                    // \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0438 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438\n                                    ruChanged = previousLanguageData.ruRuMap != null && newLanguageData.ruRuMap != null &&\n                                            previousLanguageData.ruRuMap != newLanguageData.ruRuMap\n                                    enChanged = previousLanguageData.enUsMap != null && newLanguageData.enUsMap != null &&\n                                            previousLanguageData.enUsMap != newLanguageData.enUsMap\n\n                                    if (ruChanged) {\n                                        ruDiff = computeDiff(previousLanguageData.ruRuMap, newLanguageData.ruRuMap)\n                                    }\n                                    if (enChanged) {\n                                        enDiff = computeDiff(previousLanguageData.enUsMap, newLanguageData.enUsMap)\n                                    }\n\n                                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c langFiles \u0441 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439\n                                    langFiles[mod.name] = newLanguageData\n\n                                    shouldNotify = ruChanged || enChanged\n                                }\n                            } else {\n                                // \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445\n                                println(\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                ruChanged = newLanguageData.ruRuMap != null && previousData.ruRuMap != null &&\n                                        newLanguageData.ruRuMap != previousData.ruRuMap\n                                enChanged = newLanguageData.enUsMap != null && previousData.enUsMap != null &&\n                                        newLanguageData.enUsMap != previousData.enUsMap\n\n                                if (ruChanged) {\n                                    ruDiff = computeDiff(previousData.ruRuMap!!, newLanguageData.ruRuMap!!)\n                                }\n                                if (enChanged) {\n                                    enDiff = computeDiff(previousData.enUsMap!!, newLanguageData.enUsMap!!)\n                                }\n\n                                if (ruChanged || enChanged) {\n                                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c langFiles \u0441 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439\n                                    langFiles[mod.name] = newLanguageData\n                                    shouldNotify = true\n                                }\n                            }\n                        }\n\n                        if (shouldNotify && (ruDiff != null || enDiff != null)) {\n                            println(\"\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043c\u043e\u0434\u0435 ${mod.name}. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435.\")\n                            println(\"ruDiff: $ruDiff\")\n                            println(\"enDiff: $enDiff\")\n\n                            val cacheKey = \"mod_${mod.name}_${newLanguageData.modVersion}\"\n                            val dataForCache =\n                                (newLanguageData.ruRu ?: ByteArray(0)) + (newLanguageData.enUs ?: ByteArray(0))\n                            if (!messageCache.isAlreadySent(cacheKey, dataForCache)) {\n                                sendLanguageUpdateEmbed(\n                                    modsChannel,\n                                    mod.name,\n                                    newLanguageData,\n                                    ruChanged,\n                                    enChanged,\n                                    ruDiff,\n                                    enDiff,\n                                    cacheKey,\n                                    dataForCache\n                                )\n                            } else {\n                                println(\"\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e. \u041f\u0440\u043e\u043f\u0443\u0441\u043a.\")\n                            }\n                        } else {\n                            println(\"\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e.\")\n                        }\n                    } else {\n                        println(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    }\n                } catch (e: Exception) {\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a Modrinth \u0443 \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n                }\n            }\n        }\n\n        // \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438\n        googlePlayTimer.scheduleAtFixedRate(\n            object : TimerTask() {\n                override fun run() {\n                    checkGooglePlayUpdates()\n                }\n            }, 0, 5 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 5 \u043c\u0438\u043d\u0443\u0442\n        )\n\n        modsTimer.scheduleAtFixedRate(\n            object : TimerTask() {\n                override fun run() {\n                    checkModsUpdates()\n                }\n            }, 0, 10 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 10 \u043c\u0438\u043d\u0443\u0442\n        )\n\n        // \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u044b\n        russianFeedChannel = event.jda.getTextChannelById(config.russianFeedChannelId)\n            ?: throw IllegalStateException(\"\u041a\u0430\u043d\u0430\u043b \u0441 ID ${config.russianFeedChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\")\n\n        englishFeedChannel = event.jda.getTextChannelById(config.englishFeedChannelId)\n            ?: throw IllegalStateException(\"\u041a\u0430\u043d\u0430\u043b \u0441 ID ${config.englishFeedChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\")\n\n        globalScope.launch {\n            // \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u044b\n            loadSentE621Posts()\n\n            // \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0435\u0440 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u043e\u0432\u044b\u0445 \u0430\u0440\u0442\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438\n            e621Timer.scheduleAtFixedRate(\n                object : TimerTask() {\n                    override fun run() {\n                        checkE621Updates()\n                    }\n                },\n                0,\n                10 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 10 \u043c\u0438\u043d\u0443\u0442\n            )\n        }\n\n        // \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0441\u043b\u044d\u0448-\u043a\u043e\u043c\u0430\u043d\u0434\u044b `/tontaro` \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438\n        event.jda.getGuildById(\"878019629925158932\")?.let { guild ->\n            guild.updateCommands().addCommands(\n                Commands.slash(\"tontaro\", \"\u041d\u0430\u0439\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043e\u0442 Tontaro\")\n                // \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u043f\u0446\u0438\u0438, \u0435\u0441\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\n            ).queue()\n        }\n\n        event.jda.updateCommands().queue()\n\n        val guildId = \"878019629925158932\" // \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430 ID \u0432\u0430\u0448\u0435\u0439 \u0433\u0438\u043b\u044c\u0434\u0438\u0438\n        event.jda.getGuildById(guildId)?.let { guild ->\n            guild.updateCommands().addCommands(\n                Commands.slash(\"tontaro\", \"Search random Tontaro art\"),\n                Commands.slash(\"\u0442\u043e\u043d\u0442\u0430\u0440\u043e\", \"\u041d\u0430\u0439\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0430\u0440\u0442 \u0422\u043e\u043d\u0442\u0430\u0440\u043e\")\n            ).queue(\n                { println(\"\u041a\u043e\u043c\u0430\u043d\u0434\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 ${guild.name}\") },\n                { error -> println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u0430\u043d\u0434: ${error.message}\") }\n            )\n        } ?: println(\"\u0413\u0438\u043b\u044c\u0434\u0438\u044f \u0441 ID $guildId \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430\")\n    }\n\n    // \u0412\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 BotListener \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0432\u0441\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438\n\n    private suspend fun getLanguageFiles(mod: ModConfig, client: HttpClient, apiKey: String): ModLanguageData? {\n        // \u041b\u043e\u0433\u0438\u043a\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 getLanguageFiles\n    }\n\n    private fun sendLanguageUpdateEmbed(\n        channel: NewsChannel,\n        modName: String,\n        languageData: ModLanguageData,\n        ruChanged: Boolean,\n        enChanged: Boolean,\n        ruDiff: String?,\n        enDiff: String?,\n        cacheKey: String,\n        dataForCache: ByteArray\n    ) {\n        // \u041b\u043e\u0433\u0438\u043a\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 sendLanguageUpdateEmbed\n    }\n\n    private suspend fun retryIO(\n        times: Int = 3,\n        initialDelay: Long = 100,\n        maxDelay: Long = 1000,\n        factor: Double = 2.0,\n        block: suspend () -> T\n    ): T {\n        // \u041b\u043e\u0433\u0438\u043a\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 retryIO\n    }\n\n    private fun checkMessageExists(channel: NewsChannel, messageId: String): Boolean {\n        // \u041b\u043e\u0433\u0438\u043a\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 checkMessageExists\n    }\n\n    // \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 BotListener\n    // ...\n\n    override fun onSlashCommandInteraction(event: SlashCommandInteractionEvent) {\n        val commandName = event.name\n        if (commandName == \"tontaro\" || commandName == \"\u0442\u043e\u043d\u0442\u0430\u0440\u043e\") {\n            // \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c ID \u043a\u0430\u043d\u0430\u043b\u0430\n            val channelId = event.channel.id\n            val allowedChannels = listOf(\"878021004838969376\", \"927956548200841286\")\n            if (channelId !in allowedChannels) {\n                event.reply(\"\u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0432 \u044d\u0442\u043e\u043c \u043a\u0430\u043d\u0430\u043b\u0435.\").setEphemeral(true).queue()\n                return\n            }\n\n            // \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u044b\n            globalScope.launch {\n                try {\n                    val post = fetchRandomE621Post()\n                    if (post != null && post.file.url != null) {\n                        val imageUrl = post.file.url\n                        val embed = net.dv8tion.jda.api.EmbedBuilder()\n                            // .setTitle(\"\u0421\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0430\u0440\u0442 \u043e\u0442 Tontaro\")\n                            .setImage(imageUrl)\n                            // .setUrl(\"https://e621.net/posts/${post.id}\")\n                            // .setFooter(\"\u0410\u0432\u0442\u043e\u0440: Tontaro\")\n                            .setColor(0x50FA7B)\n                            .build()\n\n                        event.replyEmbeds(embed).queue()\n                    } else {\n                        event.reply(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0430\u0440\u0442. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\").setEphemeral(true).queue()\n                    }\n                } catch (e: Exception) {\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b /$commandName: ${e.message}\")\n                    event.reply(\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\").setEphemeral(true).queue()\n                }\n            }\n        }\n    }\n\n    private suspend fun fetchRandomE621Post(): E621Post? {\n        val response: E621Response = client.get(\"https://e621.net/posts.json\") {\n            header(\"User-Agent\", config.e621UserAgent)\n            parameter(\"tags\", \"tontaro -rating:explicit order:random\")\n            parameter(\"limit\", 1)\n        }.body()\n\n        return response.posts.firstOrNull()\n    }\n\n    // \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: loadSentE621Posts, saveSentE621Posts, fetchRecentE621Posts \u0438 \u0442.\u0434.\n    // \u041f\u0435\u0440\u0435\u043d\u0435\u0441\u0438\u0442\u0435 \u0438\u0445 \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 BotListener\n\n    // \u041f\u0440\u0438\u043c\u0435\u0440 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 retryIO \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430\n    private suspend fun <T> retryIO(\n        times: Int = 3,\n        initialDelay: Long = 100,\n        maxDelay: Long = 1000,\n        factor: Double = 2.0,\n        block: suspend () -> T\n    ): T {\n        var currentDelay = initialDelay\n        repeat(times - 1) {\n            try {\n                return block()\n            } catch (e: Exception) {\n                println(\"\u041d\u0435\u0443\u0434\u0430\u0447\u043d\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430: ${e.message}\")\n                delay(currentDelay)\n                currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)\n            }\n        }\n        return block()\n    }\n\n    private fun computeDiff(oldMap: Map<String, String>, newMap: Map<String, String>): String {\n        val added = newMap.keys - oldMap.keys\n        val removed = oldMap.keys - newMap.keys\n        val changed = newMap.keys.intersect(oldMap.keys).filter { oldMap[it] != newMap[it] }\n\n        val sb = StringBuilder()\n        if (added.isNotEmpty()) {\n            sb.append(\"**\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n            added.forEach { key ->\n                sb.append(\"- `$key`: `${newMap[key]}`\\n\")\n            }\n        }\n        if (removed.isNotEmpty()) {\n            sb.append(\"**\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n            removed.forEach { key ->\n                sb.append(\"- `$key`: `${oldMap[key]}`\\n\")\n            }\n        }\n        if (changed.isNotEmpty()) {\n            sb.append(\"**\u0418\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n            changed.forEach { key ->\n                sb.append(\"- `$key`: `${oldMap[key]}` \u2192 `${newMap[key]}`\\n\")\n            }\n        }\n\n        if (sb.isEmpty()) {\n            sb.append(\"\u041d\u0435\u0442 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u0439.\")\n        }\n\n        println(\"\u0412\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f: $sb\")\n        return sb.toString()\n    }\n\n    // \u0414\u0440\u0443\u0433\u0438\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438...\n\n}\n```\n\n### **\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u043e\u0432\u0435\u0442\u044b:**\n\n1. **\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u043a\u043e\u0434\u0430:**\n   - **\u0418\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u044f:** \u0425\u0440\u0430\u043d\u0438\u0442\u0435 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u0430\u0441\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u0430.\n   - **\u041c\u043e\u0434\u0443\u043b\u044c\u043d\u043e\u0441\u0442\u044c:** \u0420\u0430\u0437\u0434\u0435\u043b\u044f\u0439\u0442\u0435 \u043a\u043e\u0434 \u043d\u0430 \u043c\u0435\u043b\u043a\u0438\u0435, \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443.\n\n2. **\u0418\u043c\u043f\u043e\u0440\u0442\u044b:**\n   - \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u043a\u043b\u0430\u0441\u0441\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Ktor \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u043d\u0443\u0436\u043d\u044b\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u044b \u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u044b.\n\n3. **\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 `null`:**\n   - \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u044b \u042d\u043b\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f `NullPointerException`.\n   - \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f `let`, `run` \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0439 Kotlin \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 `null`.\n\n4. **\u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435:**\n   - \u0412\u043c\u0435\u0441\u0442\u043e `println` \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a [Kotlin Logging](https://github.com/MicroUtils/kotlin-logging) \u0438\u043b\u0438 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.\n\n5. **\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c:**\n   - \u0411\u0443\u0434\u044c\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u044b \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0445 \u043e\u0431\u043b\u0430\u0441\u0442\u0435\u0439 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 (`globalScope`). \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f `CoroutineScope`, \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u043a \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u043e\u043c\u0443 \u0446\u0438\u043a\u043b\u0443 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0443\u0442\u0435\u0447\u0435\u043a \u043f\u0430\u043c\u044f\u0442\u0438.\n\n6. **\u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f:**\n   - \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u044f\u0432\u043d\u043e\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435 \u0442\u0438\u043f\u043e\u0432 \u0442\u0430\u043c, \u0433\u0434\u0435 \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043e\u043a \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438.\n\n### **\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435:**\n\n\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0432\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c, \u0441\u0432\u044f\u0437\u0430\u043d\u044b \u0441 \u043e\u0431\u043b\u0430\u0441\u0442\u044c\u044e \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445. \u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u044f \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u044c \u043a\u043b\u0430\u0441\u0441\u0430 `BotListener` \u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u0443\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0441\u0442\u0440\u0430\u043d\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043e\u0448\u0438\u0431\u043e\u043a. \u0422\u0430\u043a\u0436\u0435 \u0432\u0430\u0436\u043d\u043e \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0430\u043c\u0438 \u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u0442\u0438\u043f\u043e\u0432. \u0415\u0441\u043b\u0438 \u043f\u043e\u0441\u043b\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043e\u0441\u0442\u0430\u043d\u0443\u0442\u0441\u044f \u043a\u0430\u043a\u0438\u0435-\u043b\u0438\u0431\u043e \u043e\u0448\u0438\u0431\u043a\u0438, \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u043e\u0442\u043b\u0430\u0434\u043a\u0438.","behavior":"degenerate_repetition","conversation_hash":"efe4ccdc5e5a3903e4c19106b09feb8e","conversation_redacted":false,"conversation_toxic":false,"country":"Russia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"o1-mini-2024-09-12","n_chars":64138,"n_turns_in_conversation":1,"n_words":4977,"source_row_offset":3547,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:28:21 GMT","turn_id":"09a515230ced443c5d9e55c7","turn_index":0,"user_text":"\u042d\u0442\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u043c\u043e\u0435\u0433\u043e \u0431\u043e\u0442\u0430:\n\n@file:Suppress(\"UNUSED_VARIABLE\")\n\npackage ru.deflecta.auroomy\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.serialization.json.Json\nimport net.dv8tion.jda.api.JDABuilder\nimport net.dv8tion.jda.api.entities.channel.concrete.NewsChannel\nimport net.dv8tion.jda.api.entities.MessageEmbed\nimport net.dv8tion.jda.api.events.session.ReadyEvent\nimport net.dv8tion.jda.api.hooks.ListenerAdapter\nimport net.dv8tion.jda.api.utils.FileUpload\nimport io.ktor.client.*\nimport io.ktor.client.call.*\nimport io.ktor.client.engine.cio.*\nimport io.ktor.client.plugins.contentnegotiation.*\nimport io.ktor.client.request.*\nimport io.ktor.serialization.kotlinx.json.*\nimport kotlinx.serialization.encodeToString\nimport net.dv8tion.jda.api.entities.channel.concrete.TextChannel\nimport net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent\nimport net.dv8tion.jda.api.interactions.commands.build.Commands\nimport net.dv8tion.jda.api.requests.GatewayIntent\nimport java.io.*\nimport java.util.zip.ZipInputStream\nimport kotlin.system.exitProcess\nimport java.util.Timer\nimport java.util.TimerTask\nimport java.io.File\n\n// \u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 CoroutineScope\nval globalScope = CoroutineScope(Dispatchers.IO + SupervisorJob())\n\nfun main() {\n    val configContent = File(\"config.json\").readText()\n    val config = Json { ignoreUnknownKeys = true }.decodeFromString<Config>(configContent)\n\n    val jda = JDABuilder.createDefault(config.discordToken, GatewayIntent.GUILD_MESSAGES, GatewayIntent.MESSAGE_CONTENT)\n        .addEventListeners(BotListener(config))\n        .build()\n\n    // startApiServer(config)\n}\n\nclass BotListener(private val config: Config) : ListenerAdapter() {\n    private val googlePlayTimer = Timer()\n    private val modsTimer = Timer()\n\n    private val publisher = GooglePlayPublisher()\n    private val langFiles = mutableMapOf<String, ModLanguageData>()\n    private val mutex = Mutex()\n\n    private val messageCache = MessageCache()\n\n    private lateinit var googlePlayChannel: NewsChannel\n    private lateinit var modsChannel: NewsChannel\n\n    private lateinit var russianFeedChannel: TextChannel\n    private lateinit var englishFeedChannel: TextChannel\n\n    private val e621Timer = Timer()\n\n    // \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u0442 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f ID \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432\n    private val sentE621Posts = mutableSetOf<Long>()\n    private val e621PostsFile = \"e621posts.json\"\n\n    override fun onReady(event: ReadyEvent) {\n        println(\"\u0411\u043e\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d!\")\n\n        // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u044b\n        googlePlayChannel = event.jda.getNewsChannelById(config.googlePlayChannelId)\n            ?: run {\n                println(\"\u041a\u0430\u043d\u0430\u043b Google Play \u0441 ID ${config.googlePlayChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d!\")\n                exitProcess(1)\n            }\n\n        modsChannel = event.jda.getNewsChannelById(config.modsChannelId)\n            ?: run {\n                println(\"\u041a\u0430\u043d\u0430\u043b \u043c\u043e\u0434\u043e\u0432 \u0441 ID ${config.modsChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d!\")\n                exitProcess(1)\n            }\n\n        // \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0434\u043e\u0432\n        globalScope.launch {\n            for (mod in config.mods) {\n                try {\n                    println(\"\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    val newLanguageData = getLanguageFiles(mod, client, config.curseForgeApiKey)\n                    if (newLanguageData != null) {\n                        println(\"\u042f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0434\u0430 ${mod.name} \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b\")\n\n                        var shouldNotify = false\n                        var ruChanged = false\n                        var enChanged = false\n                        var ruDiff: String? = null\n                        var enDiff: String? = null\n\n                        mutex.withLock {\n                            val previousData = langFiles[mod.name]\n                            if (previousData == null) {\n                                // \u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438\n                                println(\"\u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                val previousLanguageData = getPreviousLanguageData(mod, client, config.curseForgeApiKey)\n                                if (previousLanguageData != null) {\n                                    // \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0438 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438\n                                    ruChanged = previousLanguageData.ruRuMap != null && newLanguageData.ruRuMap != null &&\n                                            previousLanguageData.ruRuMap != newLanguageData.ruRuMap\n                                    enChanged = previousLanguageData.enUsMap != null && newLanguageData.enUsMap != null &&\n                                            previousLanguageData.enUsMap != newLanguageData.enUsMap\n\n                                    if (ruChanged) {\n                                        ruDiff = computeDiff(previousLanguageData.ruRuMap, newLanguageData.ruRuMap)\n                                    }\n                                    if (enChanged) {\n                                        enDiff = computeDiff(previousLanguageData.enUsMap, newLanguageData.enUsMap)\n                                    }\n\n                                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c langFiles \u0441 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439\n                                    langFiles[mod.name] = newLanguageData\n\n                                    shouldNotify = ruChanged || enChanged\n                                }\n                            } else {\n                                // \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445\n                                println(\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                ruChanged = newLanguageData.ruRuMap != null && previousData.ruRuMap != null &&\n                                        newLanguageData.ruRuMap != previousData.ruRuMap\n                                enChanged = newLanguageData.enUsMap != null && previousData.enUsMap != null &&\n                                        newLanguageData.enUsMap != previousData.enUsMap\n\n                                if (ruChanged) {\n                                    ruDiff = computeDiff(previousData.ruRuMap!!, newLanguageData.ruRuMap!!)\n                                }\n                                if (enChanged) {\n                                    enDiff = computeDiff(previousData.enUsMap!!, newLanguageData.enUsMap!!)\n                                }\n\n                                if (ruChanged || enChanged) {\n                                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c langFiles \u0441 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439\n                                    langFiles[mod.name] = newLanguageData\n                                    shouldNotify = true\n                                }\n                            }\n                        }\n\n                        if (shouldNotify && (ruDiff != null || enDiff != null)) {\n                            println(\"\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043c\u043e\u0434\u0435 ${mod.name}. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435.\")\n                            println(\"ruDiff: $ruDiff\")\n                            println(\"enDiff: $enDiff\")\n\n                            val cacheKey = \"mod_${mod.name}_${newLanguageData.modVersion}\"\n                            val dataForCache =\n                                (newLanguageData.ruRu ?: ByteArray(0)) + (newLanguageData.enUs ?: ByteArray(0))\n                            if (!messageCache.isAlreadySent(cacheKey, dataForCache)) {\n                                withContext(Dispatchers.IO) {\n                                    sendLanguageUpdateEmbed(\n                                        modsChannel,\n                                        mod.name,\n                                        newLanguageData,\n                                        ruChanged,\n                                        enChanged,\n                                        ruDiff,\n                                        enDiff,\n                                        cacheKey,\n                                        dataForCache\n                                    )\n                                }\n                            } else {\n                                println(\"\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e. \u041f\u0440\u043e\u043f\u0443\u0441\u043a.\")\n                            }\n                        } else {\n                            println(\"\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e.\")\n                        }\n                    } else {\n                        println(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    }\n                } catch (e: Exception) {\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a Modrinth \u0443 \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n                }\n            }\n        }\n\n        // \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0430\u0439\u043c\u0435\u0440\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 e621, \u043c\u043e\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043d\u0435 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b\n        // \u041f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 Google Play\n        googlePlayTimer.scheduleAtFixedRate(\n            object : TimerTask() {\n                override fun run() {\n                    checkGooglePlayUpdates()\n                }\n            }, 0, 5 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 5 \u043c\u0438\u043d\u0443\u0442\n        )\n\n        // \u041f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u043c\u043e\u0434\u043e\u0432\n        modsTimer.scheduleAtFixedRate(\n            object : TimerTask() {\n                override fun run() {\n                    checkModsUpdates()\n                }\n            }, 0, 10 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 10 \u043c\u0438\u043d\u0443\u0442\n        )\n\n        // \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u044b\n        russianFeedChannel = event.jda.getTextChannelById(config.russianFeedChannelId)\n            ?: throw IllegalStateException(\"\u041a\u0430\u043d\u0430\u043b \u0441 ID ${config.russianFeedChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\")\n\n        englishFeedChannel = event.jda.getTextChannelById(config.englishFeedChannelId)\n            ?: throw IllegalStateException(\"\u041a\u0430\u043d\u0430\u043b \u0441 ID ${config.englishFeedChannelId} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\")\n\n        // \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0443\n        globalScope.launch {\n            // \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u044b\n            loadSentE621Posts()\n\n            // \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0435\u0440 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u043e\u0432\u044b\u0445 \u0430\u0440\u0442\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438\n            e621Timer.scheduleAtFixedRate(\n                object : TimerTask() {\n                    override fun run() {\n                        checkE621Updates()\n                    }\n                },\n                0,\n                10 * 60 * 1000 // \u041a\u0430\u0436\u0434\u044b\u0435 10 \u043c\u0438\u043d\u0443\u0442\n            )\n        }\n\n        // \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0441\u043b\u044d\u0448-\u043a\u043e\u043c\u0430\u043d\u0434\u044b `/tontaro` \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0433\u0438\u043b\u044c\u0434\u0438\u0438\n        event.jda.getGuildById(\"878019629925158932\")?.let { guild ->\n            guild.updateCommands().addCommands(\n                Commands.slash(\"tontaro\", \"\u041d\u0430\u0439\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0440\u0438\u0441\u0443\u043d\u043e\u043a \u043e\u0442 Tontaro\")\n                // \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u043f\u0446\u0438\u0438, \u0435\u0441\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\n            ).queue()\n        }\n\n        event.jda.updateCommands().queue()\n\n        val guildId = \"878019629925158932\" // \u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430 ID \u0432\u0430\u0448\u0435\u0439 \u0433\u0438\u043b\u044c\u0434\u0438\u0438\n        event.jda.getGuildById(guildId)?.let { guild ->\n            guild.updateCommands().addCommands(\n                Commands.slash(\"tontaro\", \"Search random Tontaro art\"),\n                Commands.slash(\"\u0442\u043e\u043d\u0442\u0430\u0440\u043e\", \"\u041d\u0430\u0439\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0430\u0440\u0442 \u0422\u043e\u043d\u0442\u0430\u0440\u043e\")\n            ).queue(\n                { println(\"\u041a\u043e\u043c\u0430\u043d\u0434\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 \u0433\u0438\u043b\u044c\u0434\u0438\u0438 ${guild.name}\") },\n                { error -> println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u0430\u043d\u0434: ${error.message}\") }\n            )\n        } ?: println(\"\u0413\u0438\u043b\u044c\u0434\u0438\u044f \u0441 ID $guildId \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430\")\n    }\n\n    private suspend fun loadSentE621Posts() {\n        val file = File(e621PostsFile)\n        if (file.exists()) {\n            val jsonString = file.readText()\n            val loadedPosts = Json.decodeFromString<Set<Long>>(jsonString)\n            sentE621Posts.addAll(loadedPosts)\n            println(\"\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e ${sentE621Posts.size} \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430.\")\n        } else {\n            println(\"\u0424\u0430\u0439\u043b \u0441 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u0441\u0442\u0430\u043c\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432.\")\n            try {\n                val recentPosts = fetchRecentE621Posts()\n                for (post in recentPosts) {\n                    sentE621Posts.add(post.id)\n                }\n                // \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u044b \u0432 \u0444\u0430\u0439\u043b\n                saveSentE621Posts()\n                println(\"\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043e ${sentE621Posts.size} \u043f\u043e\u0441\u0442\u043e\u0432 \u0438\u0437 e621.\")\n            } catch (e: Exception) {\n                println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 e621 \u043f\u043e\u0441\u0442\u043e\u0432: ${e.message}\")\n            }\n        }\n    }\n\n    private fun saveSentE621Posts() {\n        try {\n            val jsonString = Json.encodeToString(sentE621Posts)\n            File(e621PostsFile).writeText(jsonString)\n            println(\"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043e ${sentE621Posts.size} \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b.\")\n        } catch (e: Exception) {\n            println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u0441\u0442\u043e\u0432 e621: ${e.message}\")\n        }\n    }\n\n    private suspend fun fetchRecentE621Posts(): List<E621Post> {\n        return retryIO {\n            val response: E621Response = client.get(\"https://e621.net/posts.json\") {\n                header(\"User-Agent\", config.e621UserAgent)\n                parameter(\"tags\", \"tontaro -rating:explicit order:id_desc\")\n                parameter(\"limit\", 50)\n            }.body()\n\n            response.posts\n        }\n    }\n\n    private fun checkE621Updates() {\n        globalScope.launch {\n            try {\n                val newPosts = fetchNewE621Posts()\n                for (post in newPosts) {\n                    val imageUrl = post.file.url\n                    if (imageUrl != null) {\n                        val embed = net.dv8tion.jda.api.EmbedBuilder()\n                            // .setTitle(\"\u041d\u043e\u0432\u044b\u0439 \u0430\u0440\u0442 \u043e\u0442 Tontaro\")\n                            .setImage(imageUrl)\n                            // .setUrl(\"https://e621.net/posts/${post.id}\")\n                            // .setFooter(\"\u0410\u0432\u0442\u043e\u0440: Tontaro\")\n                            .setColor(0x50FA7B)\n                            .build()\n\n                        // \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0440\u0443\u0441\u0441\u043a\u043e\u044f\u0437\u044b\u0447\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b\n                        russianFeedChannel.sendMessageEmbeds(embed).queue()\n\n                        // \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0430\u043d\u0433\u043b\u043e\u044f\u0437\u044b\u0447\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b\n                        englishFeedChannel.sendMessageEmbeds(embed).queue()\n\n                        // \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c ID \u043f\u043e\u0441\u0442\u0430 \u0432 \u043d\u0430\u0431\u043e\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445\n                        sentE621Posts.add(post.id)\n                    }\n                }\n\n                // \u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043d\u043e\u0432\u044b\u0445 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0438\u0445 \u0432 \u0444\u0430\u0439\u043b\n                saveSentE621Posts()\n\n            } catch (e: Exception) {\n                println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 e621: ${e.message}\")\n            }\n        }\n    }\n\n    private suspend fun fetchNewE621Posts(): List<E621Post> {\n        val response: E621Response = client.get(\"https://e621.net/posts.json\") {\n            header(\"User-Agent\", config.e621UserAgent)\n            parameter(\"tags\", \"tontaro -rating:explicit order:id_asc\")\n            parameter(\"limit\", 50)\n        }.body()\n\n        // \u0424\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u0441\u0442\u044b\n        return response.posts.filter { post ->\n            !sentE621Posts.contains(post.id) && (post.rating == \"s\" || post.rating == \"q\")\n        }\n    }\n\n    private fun checkGooglePlayUpdates() {\n        val latestRelease = publisher.getLatestRelease()\n        latestRelease?.let { release ->\n            val cacheKey = \"googleplay_${release.versionCode}\"\n            val releaseData = release.releaseNotes.toByteArray()\n            val cachedData = messageCache.getMessageData(cacheKey)\n            val messageId = messageCache.getMessageId(cacheKey)\n\n            if (cachedData == null || messageId == null) {\n                // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0435 \u0431\u044b\u043b\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e \u0440\u0430\u043d\u0435\u0435 \u0438\u043b\u0438 \u043d\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e messageId\n                sendGooglePlayUpdate(release, cacheKey, releaseData)\n            } else if (!cachedData.contentEquals(releaseData)) {\n                // \u0414\u0430\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c, \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\n                val messageExists = checkMessageExists(googlePlayChannel, messageId)\n                if (messageExists) {\n                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\n                    googlePlayChannel.editMessageEmbedsById(messageId, buildEmbed(release))\n                        .setActionRow(\n                            net.dv8tion.jda.api.interactions.components.buttons.Button.link(\n                                \"https://play.google.com/store/apps/details?id=ru.deflecta.fnwb\",\n                                \"\u0421\u043a\u0430\u0447\u0430\u0442\u044c\"\n                            )\n                        )\n                        .queue()\n                    // \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043a\u0435\u0448\n                    messageCache.saveMessage(cacheKey, releaseData, messageId)\n                } else {\n                    // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043e, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043d\u043e\u0432\u043e\n                    sendGooglePlayUpdate(release, cacheKey, releaseData)\n                }\n            } else {\n                // \u0414\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c, \u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\n                val messageExists = checkMessageExists(googlePlayChannel, messageId)\n                if (!messageExists) {\n                    // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043e, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043d\u043e\u0432\u043e\n                    sendGooglePlayUpdate(release, cacheKey, releaseData)\n                }\n            }\n        }\n    }\n\n    private fun sendGooglePlayUpdate(release: ReleaseInfo, cacheKey: String, releaseData: ByteArray) {\n        googlePlayChannel.sendMessageEmbeds(buildEmbed(release))\n            .setActionRow(\n                net.dv8tion.jda.api.interactions.components.buttons.Button.link(\n                    \"https://play.google.com/store/apps/details?id=ru.deflecta.fnwb\",\n                    \"\u0421\u043a\u0430\u0447\u0430\u0442\u044c\"\n                )\n            )\n            .queue { message ->\n                messageCache.saveMessage(cacheKey, releaseData, message.id)\n            }\n    }\n\n    private fun checkModsUpdates() {\n        globalScope.launch {\n            for (mod in config.mods) {\n                try {\n                    println(\"\u041f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    val newLanguageData = getLanguageFiles(mod, client, config.curseForgeApiKey)\n                    if (newLanguageData != null) {\n                        println(\"\u042f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0434\u0430 ${mod.name} \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b\")\n\n                        var shouldNotify = false\n                        var ruChanged = false\n                        var enChanged = false\n                        var ruDiff: String? = null\n                        var enDiff: String? = null\n\n                        mutex.withLock {\n                            val previousData = langFiles[mod.name]\n                            if (previousData == null) {\n                                // \u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f: \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438\n                                println(\"\u041f\u0435\u0440\u0432\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                val previousLanguageData = getPreviousLanguageData(mod, client, config.curseForgeApiKey)\n                                if (previousLanguageData != null) {\n                                    ruChanged = previousLanguageData.ruRuMap != null && newLanguageData.ruRuMap != null &&\n                                            previousLanguageData.ruRuMap != newLanguageData.ruRuMap\n                                    enChanged = previousLanguageData.enUsMap != null && newLanguageData.enUsMap != null &&\n                                            previousLanguageData.enUsMap != newLanguageData.enUsMap\n\n                                    if (ruChanged) {\n                                        ruDiff = computeDiff(previousLanguageData.ruRuMap, newLanguageData.ruRuMap)\n                                    }\n                                    if (enChanged) {\n                                        enDiff = computeDiff(previousLanguageData.enUsMap, newLanguageData.enUsMap)\n                                    }\n\n                                    langFiles[mod.name] = newLanguageData\n\n                                    shouldNotify = ruChanged || enChanged\n                                }\n                            } else {\n                                // \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445\n                                println(\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                                ruChanged = newLanguageData.ruRuMap != null && previousData.ruRuMap != null &&\n                                        newLanguageData.ruRuMap != previousData.ruRuMap\n                                enChanged = newLanguageData.enUsMap != null && previousData.enUsMap != null &&\n                                        newLanguageData.enUsMap != previousData.enUsMap\n\n                                if (ruChanged) {\n                                    ruDiff = computeDiff(previousData.ruRuMap!!, newLanguageData.ruRuMap!!)\n                                }\n                                if (enChanged) {\n                                    enDiff = computeDiff(previousData.enUsMap!!, newLanguageData.enUsMap!!)\n                                }\n\n                                if (ruChanged || enChanged) {\n                                    langFiles[mod.name] = newLanguageData\n                                    shouldNotify = true\n                                }\n                            }\n                        }\n\n                        if (shouldNotify && (ruDiff != null || enDiff != null)) {\n                            println(\"\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043c\u043e\u0434\u0435 ${mod.name}. \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435.\")\n                            println(\"ruDiff: $ruDiff\")\n                            println(\"enDiff: $enDiff\")\n\n                            val cacheKey = \"mod_${mod.name}_${newLanguageData.modVersion}\"\n                            val dataForCache =\n                                (newLanguageData.ruRu ?: ByteArray(0)) + (newLanguageData.enUs ?: ByteArray(0))\n                            if (!messageCache.isAlreadySent(cacheKey, dataForCache)) {\n                                withContext(Dispatchers.IO) {\n                                    sendLanguageUpdateEmbed(\n                                        modsChannel,\n                                        mod.name,\n                                        newLanguageData,\n                                        ruChanged,\n                                        enChanged,\n                                        ruDiff,\n                                        enDiff,\n                                        cacheKey,\n                                        dataForCache\n                                    )\n                                }\n                            } else {\n                                println(\"\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e. \u041f\u0440\u043e\u043f\u0443\u0441\u043a.\")\n                            }\n                        } else {\n                            println(\"\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name} \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e.\")\n                        }\n                    } else {\n                        println(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n                    }\n                } catch (e: Exception) {\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n                }\n            }\n        }\n\n    private fun buildLanguageEmbed(\n        modName: String,\n        languageData: ModLanguageData,\n        ruChanged: Boolean,\n        enChanged: Boolean\n    ): net.dv8tion.jda.api.EmbedBuilder {\n        val embedBuilder = net.dv8tion.jda.api.EmbedBuilder()\n            .setTitle(\"\u041c\u043e\u0434 $modName \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f!\")\n            .setDescription(\"\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u044b.\")\n            .addField(\"\u0412\u0435\u0440\u0441\u0438\u044f \u043c\u043e\u0434\u0430\", languageData.modVersion ?: \"\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\", true)\n            .setColor(0x50FA7B)\n\n        val changesDescription = when {\n            ruChanged && enChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c \u043e\u0431\u0430 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u0430: `ru_ru` \u0438 `en_us`.\"\n            ruChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e `ru_ru`.\"\n            enChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e `en_us`.\"\n            else -> \"\u041d\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u0430\u0445.\"\n        }\n\n        embedBuilder.addField(\"\u041a\u0430\u043a\u0438\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c\", changesDescription, false)\n        return embedBuilder\n    }\n\n    private fun buildAttachments(modName: String, languageData: ModLanguageData): List<FileUpload> {\n        val attachments = mutableListOf<FileUpload>()\n        languageData.ruRu?.let {\n            attachments.add(FileUpload.fromData(it, \"${modName}_ru_ru.json\"))\n        }\n        languageData.enUs?.let {\n            attachments.add(FileUpload.fromData(it, \"${modName}_en_us.json\"))\n        }\n        return attachments\n    }\n\n    private fun checkMessageExists(channel: NewsChannel, messageId: String): Boolean {\n        return try {\n            val message = channel.retrieveMessageById(messageId).complete()\n            message != null\n        } catch (e: Exception) {\n            // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430\n            false\n        }\n    }\n\n    private fun sendLanguageUpdateEmbed(\n        channel: NewsChannel,\n        modName: String,\n        languageData: ModLanguageData,\n        ruChanged: Boolean,\n        enChanged: Boolean,\n        ruDiff: String?,\n        enDiff: String?,\n        cacheKey: String,\n        dataForCache: ByteArray\n    ) {\n        println(\"\u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u043c\u043e\u0434a $modName\")\n\n        val embedBuilder = net.dv8tion.jda.api.EmbedBuilder()\n            .setTitle(\"\u041c\u043e\u0434 $modName \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f!\")\n            .setDescription(\"\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u044b.\")\n            .addField(\"\u0412\u0435\u0440\u0441\u0438\u044f \u043c\u043e\u0434\u0430\", languageData.modVersion ?: \"\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\", true)\n            .setColor(0x50FA7B)\n\n        val changesDescription = when {\n            ruChanged && enChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c \u043e\u0431\u0430 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u0430: `ru_ru` \u0438 `en_us`.\"\n            ruChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e `ru_ru`.\"\n            enChanged -> \"\u0418\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e `en_us`.\"\n            else -> \"\u041d\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u0430\u0445.\"\n        }\n\n        embedBuilder.addField(\"\u041a\u0430\u043a\u0438\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c\", changesDescription, false)\n\n        if (ruChanged && ruDiff != null) {\n            embedBuilder.addField(\"\u0420\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0432 `ru_ru`:\", \"```diff\\n$ruDiff\\n```\", false)\n        }\n\n        if (enChanged && enDiff != null) {\n            embedBuilder.addField(\"\u0420\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0432 `en_us`:\", \"```diff\\n$enDiff\\n```\", false)\n        }\n\n        channel.sendMessageEmbeds(embedBuilder.build())\n            .addFiles(buildAttachments(modName, languageData))\n            .queue(\n                { message ->\n                    println(\"\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u043c\u043e\u0434\u0430 $modName \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e: ${message.id}\")\n                    messageCache.saveMessage(cacheKey, dataForCache, message.id)\n                },\n                { error ->\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u043c\u043e\u0434\u0430 $modName: ${error.message}\")\n                }\n            )\n    }\n\n    private fun buildEmbed(release: ReleaseInfo): MessageEmbed {\n        val releaseType = when (release.releaseType) {\n            \"internal\" -> \"\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\"\n            \"alpha\" -> \"\u0410\u043b\u044c\u0444\u0430\"\n            \"beta\" -> \"\u0411\u0435\u0442\u0430\"\n            \"production\" -> \"\u0420\u0435\u043b\u0438\u0437\"\n            else -> release.releaseType\n        }\n        val embedBuilder = net.dv8tion.jda.api.EmbedBuilder()\n            .setTitle(\"\u0412\u044b\u0448\u043b\u0430 \u043d\u043e\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u043e\u0432\")\n            .setDescription(\"\u041f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0451!\")\n            .addField(\"\u0412\u0435\u0440\u0441\u0438\u044f\", release.versionName, true)\n            .addField(\"\u0422\u0438\u043f \u0432\u044b\u043f\u0443\u0441\u043a\u0430\", releaseType, true)\n            .addField(\"\u0421\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\", release.releaseNotes, false)\n            .setThumbnail(\"https://lh3.googleusercontent.com/g-SDH_bNyQg5IZgsO0Ft1enmz72Cm-e94nOb-wLu3JtSISKmQk-sO_2atrv8K8TAkg\")\n            .setColor(0x50FA7B)\n        return embedBuilder.build()\n    }\n\n        private suspend fun getLanguageFiles(mod: ModConfig, client: HttpClient, apiKey: String): ModLanguageData? {\n            // \u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u043d\u0430\u0439\u0442\u0438 \u043c\u043e\u0434 \u043d\u0430 Modrinth\n            try {\n                if (!mod.modrinthId.isNullOrBlank()) {\n                    // \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 Modrinth\n                    val versions = client.get(\"https://api.modrinth.com/v2/project/${mod.modrinthId}/version\").body<List<ModrinthVersion>>()\n                    if (versions.isNotEmpty()) {\n                        // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u0438\n                        val latestVersion = versions.first()\n                        val previousVersion = if (versions.size >= 2) versions[1] else null\n\n                        // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438\n                        val latestLangData = getLanguageFilesForVersion(mod, latestVersion, client, apiKey)\n\n                        // \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 (\u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c)\n                        val previousLangData = previousVersion?.let { getLanguageFilesForVersion(mod, it, client, apiKey) }\n\n                        // \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043e\u0431\u0435 \u0432\u0435\u0440\u0441\u0438\u0438, \u043c\u043e\u0436\u043d\u043e \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0437\u0434\u0435\u0441\u044c \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0438\u0445\n                        return latestLangData\n                    }\n                } else {\n                    // \u041f\u043e\u0438\u0441\u043a \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u043d\u0430 Modrinth\n                    val modrinthDataByName = searchModrinthByName(mod.name, client)\n                    if (modrinthDataByName != null) return modrinthDataByName\n                }\n            } catch (e: Exception) {\n                println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a Modrinth \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n                // \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u043a CurseForge\n            }\n\n            // \u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u043d\u0430\u0439\u0442\u0438 \u043c\u043e\u0434 \u043d\u0430 CurseForge\n            try {\n                if (!mod.curseForgeId.isNullOrBlank()) {\n                    // \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0434\u0430\n                    val filesResponse: CurseForgeFileResponse =\n                        client.get(\"https://api.curseforge.com/v1/mods/$curseForgeId/files\") {\n                            header(\"x-api-key\", apiKey)\n                        }.body()\n\n                    if (filesResponse.data.isNotEmpty()) {\n                        val latestFile = filesResponse.data.first { it.downloadUrl != null }\n                        val previousFile = if (filesResponse.data.size >= 2) filesResponse.data[1] else null\n\n                        val latestLangData = getLanguageFilesForVersion(mod, latestFile, client, apiKey)\n                        val previousLangData = previousFile?.let { getLanguageFilesForVersion(mod, it, client, apiKey) }\n\n                        return latestLangData\n                    }\n                } else {\n                    // \u041f\u043e\u0438\u0441\u043a \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u043d\u0430 CurseForge\n                    val curseForgeDataByName = searchCurseForgeByName(mod.name, client, apiKey)\n                    if (curseForgeDataByName != null) return curseForgeDataByName\n                }\n            } catch (e: Exception) {\n                println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u043a CurseForge \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n            }\n\n            // \u0415\u0441\u043b\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e\n            return null\n        }\n\n    private suspend fun getModrinthLanguageFiles(modrinthId: String, client: HttpClient): ModLanguageData? {\n        return retryIO(times = 5, initialDelay = 1000) { // \u041f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c 5 \u0440\u0430\u0437 \u0441 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u043e\u0439 1 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\n        // \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0439 \u043c\u043e\u0434\u0430\n        val versions: List<ModrinthVersion> =\n            client.get(\"https://api.modrinth.com/v2/project/$modrinthId/version\").body()\n        if (versions.isEmpty()) return@retryIO null\n\n        // \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c, \u0447\u0442\u043e \u043f\u0435\u0440\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0441\u0430\u043c\u0430\u044f \u043d\u043e\u0432\u0430\u044f\n        val latestVersion = versions.first()\n        val fileInfo = latestVersion.files.find { it.primary } ?: latestVersion.files.firstOrNull() ?: return@retryIO null\n\n        // \u0421\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 JAR-\u0444\u0430\u0439\u043b\u0430\n        val jarBytes: ByteArray = client.get(fileInfo.url).body()\n\n        // \u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\n        val languageData = extractLanguageFiles(jarBytes)\n\n        // \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0441 \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438\n        val updatedLanguageData = languageData.copy(\n            modVersion = latestVersion.version_number,\n            releaseDate = latestVersion.date_published\n        )\n\n            return@retryIO updatedLanguageData\n        }\n    }\n\n    private suspend fun searchModrinthByName(name: String, client: HttpClient): ModLanguageData? {\n        val searchResponse: ModrinthSearchResponse = client.get(\"https://api.modrinth.com/v2/search\") {\n            parameter(\"query\", name)\n            parameter(\"limit\", 1)\n        }.body()\n\n        if (searchResponse.hits.isEmpty()) return null\n\n        return getModrinthLanguageFiles(searchResponse.hits[0].id, client)\n    }\n\n    private suspend fun getCurseForgeLanguageFiles(\n        curseForgeId: String,\n        client: HttpClient,\n        apiKey: String\n    ): ModLanguageData? {\n        // \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u043c\u043e\u0434\u0430\n        val filesResponse: CurseForgeFileResponse =\n            client.get(\"https://api.curseforge.com/v1/mods/$curseForgeId/files\") {\n                header(\"x-api-key\", apiKey)\n            }.body()\n\n        if (filesResponse.data.isEmpty()) return null\n\n        // \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c, \u0447\u0442\u043e \u043f\u0435\u0440\u0432\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u0430\u043c\u044b\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439\n        val fileData = filesResponse.data.firstOrNull { !it.downloadUrl.isNullOrBlank() } ?: return null\n        val downloadUrl = fileData.downloadUrl!!\n\n        // \u0421\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 JAR-\u0444\u0430\u0439\u043b\u0430\n        val jarBytes: ByteArray = client.get(downloadUrl).body()\n\n        // \u0418\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u044f\u0437\u044b\u043a\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\n        val languageData = extractLanguageFiles(jarBytes)\n\n        // \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0441 \u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438\n        val updatedLanguageData = languageData.copy(\n            modVersion = fileData.displayName,\n            releaseDate = fileData.fileDate\n        )\n\n        return updatedLanguageData\n    }\n\n    private suspend fun searchCurseForgeByName(\n        name: String,\n        client: HttpClient,\n        apiKey: String\n    ): ModLanguageData? {\n        val searchResponse: CurseForgeSearchResponse = client.get(\"https://api.curseforge.com/v1/mods/search\") {\n            header(\"x-api-key\", apiKey)\n            parameter(\"gameId\", 432) // ID \u0438\u0433\u0440\u044b Minecraft\n            parameter(\"searchFilter\", name)\n            parameter(\"pageSize\", 1)\n        }.body()\n\n        if (searchResponse.data.isEmpty()) return null\n\n        val modId = searchResponse.data.first().id\n        return getCurseForgeLanguageFiles(modId.toString(), client, apiKey)\n    }\n\n    private suspend fun extractLanguageFiles(jarBytes: ByteArray): ModLanguageData {\n        val jarInputStream = ZipInputStream(jarBytes.inputStream())\n        var ruRuBytes: ByteArray? = null\n        var enUsBytes: ByteArray? = null\n        var modVersion: String? = null\n        var releaseDate: String? = null\n\n        var entry = jarInputStream.nextEntry\n        while (entry != null) {\n            val name = entry.name.lowercase()\n            when {\n                name.matches(Regex(\"\"\".*/lang/ru_ru\\.(json|lang|properties)$\"\"\")) -> {\n                    val baos = ByteArrayOutputStream()\n                    jarInputStream.copyTo(baos)\n                    ruRuBytes = baos.toByteArray()\n                }\n\n                name.matches(Regex(\"\"\".*/lang/en_us\\.(json|lang|properties)$\"\"\")) -> {\n                    val baos = ByteArrayOutputStream()\n                    jarInputStream.copyTo(baos)\n                    enUsBytes = baos.toByteArray()\n                }\n\n                name.matches(Regex(\"\"\".*/mods\\.toml$\"\"\")) -> {\n                    val baos = ByteArrayOutputStream()\n                    jarInputStream.copyTo(baos)\n                    val modsTomlContent = baos.toString(\"UTF-8\")\n                    modVersion = parseModVersionFromModsToml(modsTomlContent)\n                }\n            }\n            jarInputStream.closeEntry()\n            if (ruRuBytes != null && enUsBytes != null && modVersion != null) {\n                break\n            }\n            entry = jarInputStream.nextEntry\n        }\n        jarInputStream.close()\n\n        val ruRuMap = ruRuBytes?.let { parseLanguageFile(it) }\n        val enUsMap = enUsBytes?.let { parseLanguageFile(it) }\n\n        return ModLanguageData(ruRuBytes, enUsBytes, ruRuMap, enUsMap, modVersion, releaseDate)\n    }\n\n    private fun parseModVersionFromModsToml(modsTomlContent: String): String? {\n        val versionRegex = Regex(\"\"\"\\s*version\\s*=\\s*\"([^\"]+)\"\"\"\")\n        val matchResult = versionRegex.find(modsTomlContent)\n        return matchResult?.groups?.get(1)?.value\n    }\n\n    override fun onSlashCommandInteraction(event: SlashCommandInteractionEvent) {\n        val commandName = event.name\n        if (commandName == \"tontaro\" || commandName == \"\u0442\u043e\u043d\u0442\u0430\u0440\u043e\") {\n            // \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c ID \u043a\u0430\u043d\u0430\u043b\u0430\n            val channelId = event.channel.id\n            val allowedChannels = listOf(\"878021004838969376\", \"927956548200841286\")\n            if (channelId !in allowedChannels) {\n                event.reply(\"\u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0432 \u044d\u0442\u043e\u043c \u043a\u0430\u043d\u0430\u043b\u0435.\").setEphemeral(true).queue()\n                return\n            }\n\n            // \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u044b\n            globalScope.launch {\n                try {\n                    val post = fetchRandomE621Post()\n                    if (post != null && post.file.url != null) {\n                        val imageUrl = post.file.url\n                        val embed = net.dv8tion.jda.api.EmbedBuilder()\n                            // .setTitle(\"\u0421\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0430\u0440\u0442 \u043e\u0442 Tontaro\")\n                            .setImage(imageUrl)\n                            // .setUrl(\"https://e621.net/posts/${post.id}\")\n                            // .setFooter(\"\u0410\u0432\u0442\u043e\u0440: Tontaro\")\n                            .setColor(0x50FA7B)\n                            .build()\n\n                        event.replyEmbeds(embed).queue()\n                    } else {\n                        event.reply(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0430\u0440\u0442. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\").setEphemeral(true).queue()\n                    }\n                } catch (e: Exception) {\n                    println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b /$commandName: ${e.message}\")\n                    event.reply(\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u043e\u0437\u0436\u0435.\").setEphemeral(true).queue()\n                }\n            }\n        }\n    }\n\n    private suspend fun fetchRandomE621Post(): E621Post? {\n        val response: E621Response = client.get(\"https://e621.net/posts.json\") {\n            header(\"User-Agent\", config.e621UserAgent)\n            parameter(\"tags\", \"tontaro -rating:explicit order:random\")\n            parameter(\"limit\", 1)\n        }.body()\n\n        return response.posts.firstOrNull()\n    }\n\n    private suspend fun <T> retryIO(\n        times: Int = 3,\n        initialDelay: Long = 100,\n        maxDelay: Long = 1000,\n        factor: Double = 2.0,\n        block: suspend () -> T\n    ): T {\n        var currentDelay = initialDelay\n        repeat(times - 1) {\n            try {\n                return block()\n            } catch (e: Exception) {\n                println(\"\u041d\u0435\u0443\u0434\u0430\u0447\u043d\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430: ${e.message}\")\n                delay(currentDelay)\n                currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)\n            }\n        }\n        return block()\n    }\n\n    private val client = HttpClient(CIO) {\n        install(ContentNegotiation) {\n            json(Json { ignoreUnknownKeys = true })\n        }\n        engine {\n            requestTimeout = 20000 // 20 \u0441\u0435\u043a\u0443\u043d\u0434\n        }\n    }\n}\n\n// \u041e\u0431\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438\n\nprivate fun ensureMessage(\n    channel: NewsChannel,\n    cacheKey: String,\n    dataForCache: ByteArray,\n    messageCache: MessageCache,\n    buildEmbed: () -> MessageEmbed,\n    buildAttachments: () -> List<FileUpload> = { emptyList() }\n) {\n    val cachedData = messageCache.getMessageData(cacheKey)\n    val messageId = messageCache.getMessageId(cacheKey)\n\n    if (cachedData == null || messageId == null || !cachedData.contentEquals(dataForCache)) {\n        sendOrUpdateMessage(channel, cacheKey, dataForCache, messageCache, buildEmbed, buildAttachments)\n    } else {\n        if (!doesMessageExist(channel, messageId)) {\n            sendOrUpdateMessage(channel, cacheKey, dataForCache, messageCache, buildEmbed, buildAttachments)\n        }\n    }\n}\n\nprivate fun sendOrUpdateMessage(\n    channel: NewsChannel,\n    cacheKey: String,\n    dataForCache: ByteArray,\n    messageCache: MessageCache,\n    buildEmbed: () -> MessageEmbed,\n    buildAttachments: () -> List<FileUpload>\n) {\n    channel.sendMessageEmbeds(buildEmbed())\n        .addFiles(buildAttachments())\n        .queue { message ->\n            messageCache.saveMessage(cacheKey, dataForCache, message.id)\n        }\n}\n\nprivate fun doesMessageExist(channel: NewsChannel, messageId: String): Boolean {\n    return try {\n        val message = channel.retrieveMessageById(messageId).complete()\n        message != null\n    } catch (e: Exception) {\n        // \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0438\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430\n        false\n    }\n}\n\nprivate fun parseLanguageFile(bytes: ByteArray): Map<String, String> {\n    val jsonString = bytes.toString(Charsets.UTF_8)\n    return try {\n        Json.decodeFromString(jsonString)\n    } catch (e: Exception) {\n        println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0435 \u044f\u0437\u044b\u043a\u043e\u0432\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430: ${e.message}\")\n        emptyMap()\n    }\n}\n\nprivate fun computeDiff(oldMap: Map<String, String>, newMap: Map<String, String>): String {\n    val added = newMap.keys - oldMap.keys\n    val removed = oldMap.keys - newMap.keys\n    val changed = newMap.keys.intersect(oldMap.keys).filter { oldMap[it] != newMap[it] }\n\n    val sb = StringBuilder()\n    if (added.isNotEmpty()) {\n        sb.append(\"**\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n        added.forEach { key ->\n            sb.append(\"- `$key`: `${newMap[key]}`\\n\")\n        }\n    }\n    if (removed.isNotEmpty()) {\n        sb.append(\"**\u0423\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n        removed.forEach { key ->\n            sb.append(\"- `$key`: `${oldMap[key]}`\\n\")\n        }\n    }\n    if (changed.isNotEmpty()) {\n        sb.append(\"**\u0418\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438:**\\n\")\n        changed.forEach { key ->\n            sb.append(\"- `$key`: `${oldMap[key]}` \u2192 `${newMap[key]}`\\n\")\n        }\n    }\n\n    if (sb.isEmpty()) {\n        sb.append(\"\u041d\u0435\u0442 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u0439.\")\n    }\n\n    println(\"\u0412\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f: $sb\")\n    return sb.toString()\n}\n\nprivate suspend fun getPreviousLanguageData(mod: ModConfig, client: HttpClient, apiKey: String): ModLanguageData? {\n    return try {\n        val versions = getAllModVersions(mod, client, apiKey)\n        if (versions.size < 2) return null // \u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u0435\u0440\u0441\u0438\u0439 \u0434\u043b\u044f \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f\n\n        val previousVersion = versions[1] // \u0412\u0442\u043e\u0440\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f - \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0430\u044f\n        getLanguageFilesForVersion(mod, previousVersion, client, apiKey)\n    } catch (e: Exception) {\n        println(\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}: ${e.message}\")\n        null\n    }\n}\n\nprivate suspend fun getAllModVersions(mod: ModConfig, client: HttpClient, apiKey: String): List<ModrinthVersion> {\n    // \u041f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f Modrinth\n    if (!mod.modrinthId.isNullOrBlank()) {\n        return client.get(\"https://api.modrinth.com/v2/project/${mod.modrinthId}/version\").body()\n    }\n    // \u041f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f CurseForge\n    // \u0420\u0435\u0430\u043b\u0438\u0437\u0443\u0439\u0442\u0435 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0434\u043b\u044f CurseForge, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0435\u0440\u0441\u0438\u0439\n    return emptyList()\n}\n\nprivate suspend fun getLanguageFilesForVersion(\n    mod: ModConfig,\n    version: Any, // \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c ModrinthVersion \u0438\u043b\u0438 CurseForgeFileData\n    client: HttpClient,\n    apiKey: String\n): ModLanguageData? {\n    return when (version) {\n        is ModrinthVersion -> getLanguageFilesForVersion(mod, version, client, apiKey)\n        is CurseForgeFileData -> getLanguageFilesForVersion(mod, version, client, apiKey)\n        else -> {\n            println(\"\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0442\u0438\u043f \u0432\u0435\u0440\u0441\u0438\u0438 \u0434\u043b\u044f \u043c\u043e\u0434\u0430 ${mod.name}\")\n            null\n        }\n    }\n}\n\n\u042f \u043f\u043e\u043b\u0443\u0447\u0430\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438:\n90\nUnresolved reference: getLanguageFiles\n105\nUnresolved reference: client\n114\nType mismatch: inferred type is Map<String, String>? but Map<String, String> was expected\n158\nUnresolved reference: sendLanguageUpdateEmbed\n284\nUnresolved reference: retryIO\n286\n<html>Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:<br/>public fun HttpMessageBuilder.header(key: String, value: Any?): Unit defined in io.ktor.client.request\n356\nUnresolved reference: checkMessageExists\n\n\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u044d\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430!"},{"assistant_initiates":false,"asst_text":"\u0427\u0435\u043c \u044f \u043c\u043e\u0433\u0443 \u0432\u0430\u043c \u043f\u043e\u043c\u043e\u0447\u044c \u0441\u0435\u0433\u043e\u0434\u043d\u044f?","behavior":"degenerate_repetition","conversation_hash":"9e774bbde762cddb1d5d7e55c549bb1b","conversation_redacted":false,"conversation_toxic":false,"country":"Latvia","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"Russian","model":"gpt-4o-2024-08-06","n_chars":167,"n_turns_in_conversation":1,"n_words":22,"source_row_offset":3548,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:28:24 GMT","turn_id":"6bad552e386135d8958d03d7","turn_index":0,"user_text":"Assistant: \nUser: User: \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435.\nAssistant: \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0439 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435.\nAssistant:\nAssistant:"},{"assistant_initiates":false,"asst_text":"Creating a script for advanced multi-frame super-resolution similar to Google's Handheld Multi-Frame Super-Resolution is a complex task, particularly when dealing with raw image formats like Sony's `.ARW` files. Implementing a highly effective algorithm would typically involve advanced techniques in machine learning, computer vision, and considerable resources in terms of data and computational power.\n\nHowever, I can guide you through building a simplified version of a super-resolution pipeline using available libraries and neural networks. This will involve:\n\n1. **Reading `.ARW` files.**\n2. **Performing multi-frame alignment and super-resolution.**\n3. **Outputting as `.tiff` or `.dng` files.**\n\nWe\u2019ll use Python with several libraries:\n- `rawpy` for handling raw `.ARW` files.\n- `opencv` for image processing and alignment.\n- `numpy` for numerical operations.\n- `PIL` (Pillow) for saving images.\n- Pre-trained models from libraries like `tensorflow` or `pytorch` for super-resolution (you might consider models like EDSR or similar).\n\nHere's a simplified version:\n\n```python\nimport os\nimport rawpy\nimport numpy as np\nimport cv2\nfrom PIL import Image\nimport torch\nfrom torchvision import transforms\nimport torchvision.models as models\n\ndef load_raw_image(file_path):\n    with rawpy.imread(file_path) as raw:\n        rgb = raw.postprocess()\n    return rgb\n\ndef align_images(images):\n    # Initialize first image\n    base_image = images[0]\n    \n    # Create an empty list for aligned images\n    aligned_images = [base_image]\n\n    # Align all subsequent images\n    for img in images[1:]:\n        # Find the transformation matrix (this is a simple example)\n        # Note: More sophisticated alignment methods should be used.\n        matrix = cv2.estimateAffinePartial2D(base_image.reshape(-1, 2), img.reshape(-1, 2))[0]\n        aligned_img = cv2.warpAffine(img, matrix, (img.shape[1], img.shape[0]))\n        aligned_images.append(aligned_img)\n\n    return np.array(aligned_images)\n\ndef super_resolve(images):\n    model = models.edsr()  # load a pre-trained EDSR model\n    model.eval()\n    \n    # Normalize images\n    images_tensor = torch.Tensor(images).permute(0, 3, 1, 2)  # Change shape to [N, C, H, W]\n    with torch.no_grad():\n        sr_images = model(images_tensor)\n    \n    return sr_images.permute(0, 2, 3, 1).numpy()  # Change back to [N, H, W, C]\n\ndef save_image(image, output_path):\n    pil_img = Image.fromarray(image)\n    pil_img.save(output_path)\n\ndef process_images(input_files, output_folder):\n    images = [load_raw_image(file) for file in input_files]\n    aligned_images = align_images(images)\n    sr_images = super_resolve(aligned_images)\n\n    for idx, sr_img in enumerate(sr_images):\n        output_path = os.path.join(output_folder, f'result_{idx}.tiff')\n        save_image(sr_img.astype(np.uint8), output_path)\n\ndef main(input_folder, output_folder):\n    input_files = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if f.endswith('.ARW')]\n    os.makedirs(output_folder, exist_ok=True)\n    process_images(input_files, output_folder)\n\nif __name__ == \"__main__\":\n    input_folder = 'path_to_your_input_directory'  # set your input folder path\n    output_folder = 'path_to_your_output_directory'  # set your output folder path\n    main(input_folder, output_folder)\n```\n\n### Notes:\n1. **Alignment**: The alignment step is simplified. For practical purposes, you should consider using more advanced techniques such as SIFT, ORB, or feature-based matching.\n2. **Super-resolution Model**: The model used here is a placeholder (`models.edsr()`). You will need to integrate a suitable super-resolution model manually. Libraries such as TensorFlow or PyTorch allow for the loading and use of pre-trained models.\n3. **Raw Image Processing**: The handling of `.ARW` files may require specific considerations. Make sure libraries such as `rawpy` support your specific raw format correctly.\n4. **Environmental Setup**: Ensure you have the necessary Python packages installed (use `pip install rawpy opencv-python numpy pillow torch torchvision`).\n5. **DNG Format**: Converting images to DNG format is more complex as it involves RAW processing. You might need specialized libraries or command-line tools for that.\n\nThis script provides a framework and starting point, but optimizations and enhancements will be necessary to achieve the quality and performance similar to state-of-the-art techniques.","behavior":"degenerate_repetition","conversation_hash":"339e7a8a2af04a2770cab6ef41b9147f","conversation_redacted":false,"conversation_toxic":false,"country":"United States","empty_output":false,"evidence_codes":[],"label":"absent","label_present":false,"label_qualified":false,"label_source":"Final dense release","language":"English","model":"gpt-4o-mini-2024-07-18","n_chars":4662,"n_turns_in_conversation":2,"n_words":566,"source_row_offset":3549,"source_shard":"turns-00031.parquet","timestamp":"Fri, 04 Oct 2024 06:29:04 GMT","turn_id":"5465173364e159e8d58c0d0a","turn_index":0,"user_text":"Please write a python script that could function akin to Google's Handheld Multi-Frame Super-Resolution research, if not better. In this case, I would like to be able to input Sony .ARW files, and output either .tiff or .dng files."}],"shard":{"index":31,"name":"turns-00031.parquet","rows":51455}}
