From 50d505f887edefcc0be6c364778fa97673f68e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=28=D0=A5?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D1=8B=D1=87=D0=AA=29=20=D0=A5=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Mon, 11 Aug 2025 16:12:47 +0300 Subject: [PATCH] =?UTF-8?q?-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B4=D0=BB=D1=8F=20systemd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LG_news.session-journal | Bin 12824 -> 0 bytes bot-news-linux-gaming.service | 33 ++++++++ discord_client.py | 46 ++++++++-- install-service.sh | 127 ++++++++++++++++++++++++++++ service-control.sh | 155 ++++++++++++++++++++++++++++++++++ telegram_client.py | 90 +++++++++++++++----- uninstall-service.sh | 68 +++++++++++++++ vk_client.py | 2 +- 8 files changed, 492 insertions(+), 29 deletions(-) delete mode 100644 LG_news.session-journal create mode 100644 bot-news-linux-gaming.service create mode 100755 install-service.sh create mode 100755 service-control.sh create mode 100755 uninstall-service.sh diff --git a/LG_news.session-journal b/LG_news.session-journal deleted file mode 100644 index 48f1a4ac8bad5e72b3d2567b5fdcc11bd7736887..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12824 zcmeHNXH=9|w|;?l=ta?p28kGhBq9Q$q5}5b6Z2_|dM5)6FiyiM6idRupx8h`QB*`! zj5Q!dkgBnZWk#c)?NcMsL{VdjC9&OSpBWI1cYSN!Uw7Sm%?iv6v-de?muEkFpO@p_ z{Oh`H&3e3R$#ItW)#A+`{{PkgW(zntIrQLqS3J%x3-gQ?Q9O(%(oe2{k!##*56qVu;v7N1xkSr{|8^h&*c3qM_Jli)LCq>h_>+O zUUP?SwarZI_{!qjW^do~((1i`w-#q(hPf1Be zq*^0VNYpY>Y?Ov`U0CeZ!jNZ7G87uB4dq6ip%Sl(JPjN0W`Us;A66J@q5`ZM2I|gi z%;*GF9R#EK@@03w2z@gBFNIPi5zAyEe{QlUb=h=7hN0Y00cADDM98Om8gikn(zw_- zpZ-8^P!IM>K^%Re?0VCh0nL!P0A zc7Scw7>W#qq@ug-!CZI`N)UK4d$06f4Ba7A8spF+q3_WVKugkEn}HVPb^I<7U7LqGC)_i@8(oU z`U*Vwe@s>^*)YoKkT_Bzr#*{fqr?iy4D5TR*lwXAi;QMWrv?3V$Nsq917hyT%w6Jh zTP7CY>Z^{Hh*Sw`eA}r^JX$4DYa|Mh=Xe#JpM*k^vVS_+7eXCzx(v~s+WK$Y)Ub|B z8Q_oqIM;PcrwlNx!1m~r)t<0>VoZZ*bnx=bvQ ziKLC8;J0!Ak)^8@i^8zA~&`t;Xq;o-bp%fVkIeH7eVCXP{wrAox=y2Z;S2+Trb z5+W+y6d(C;TcM%qO>~S-TCj0dTj=mZ$p3g%oZIH=-Ig*eEfcG?l5j}`{HBiPTw^1K z4l}GZ>JY8X!v1@D1O4fy5TH%%qY1r*07vep&U98ewoOaWMc{) ziPrCvl$5r92;{v5fxMH&+CQ~7pVW&K3XxDQl#68wiIj7VKAHO5upS<)FeXAaiL$_R zL^O$qCh49%4WR9FB7Rkn#giM=FCDO86;7JOVjk|x#QJ51TD+|>RC!_>)kZx#NfoK^ zPD*@okAsSK2>;t%)MqOXbofE;FH{DquynW5@laO|g)BSA>RvCa`V?Odgt=Z8W>)Tq zIua%qsSxF{arZAyGsLBbD*cRh#Vb?vC=9 zea$&;GD=6?i%V-Q*m*qo0b#yFCue*!pL30x<1RC-qD(e{L7?vW(G64ZHE)48AIA%A zHr;aS6{D2F9YS?@v`90HbDeE}b^8SNk>^DA5$(38?pbNlSO~gvgZW`qW7Gc{SfG|6 z=7NO1noP~&brZ}~!t&o7vBA10wg2h}Q39av?Ix;=qE|}AF-k>(NKB5O`8;R^0EFO` zxqS_yu8hJRvA}?Hi0KMAce~A(1dvD3z#TcG45$w_Ms7KvZl9 z#C1^0aNA_1zPh`Y_OyW}O5Q`RQ{3{-K5Rg($Ua+6lE@bwnby;ggRmnkpz_hI>h;py zIbSph@*Rd{UNwd6!&nyGM()HSSp5DsnL&=%s_ zE%vEr->NXN6sRoHC}V|U7-VJf-iv=jW+|*{gK@~%lPj$EdxZ?(O?#OA+CBhCK zOTw3nt?s@}-x=$4eU)>=Vf((itK}&z=FO zO3}UUzGiSpPGn%-OLr?X&kiyiQf8v_x{t;`>Q@GXRYmnRtRo-6-f$Wv8@Y`DgoOZ^ z4xFmbe~VYO(*1V7fCvwy`Gh_HT43LPcH8Lei@sm#eqEN-pPg6Bdwe)xZ)EE)h4JyC z*!XzloH9hW|9yU-Dp^AtM1iEul@qXJ;3WZzIob#6ZoX_7%`}hax3{^t`($37GB#Wt zE>ep*S6R=7?&cVU^@-1v8&DotsZ3%{G2@e`?nc&c9#G@Kx$$ZH^Dg9X_gbY^M@N8M zsE5!GGK`<@M)Ixh_?8zOal2~tq?jwCI*X%};bMhGB?+H7lXFcyaD1a-6)Z|jj^TG7 zU48DxFCo-{8_xS##~<6)wqrZFM5R<97-jDU3?VAjf$GKs_SMxVucLJKKstwx8#6Dv z!A>m~QZDxHg&6*M!;j31CdfoJS&Py^<;#qF{<`|aN4}8v=6reg6BDe@&Gh<6BooIA zV`SP{(I{X8Wn!sXoZvaZ{GLuaK=<~%v(>pF!Mvm!6Tr>hAQK-H$MGXcd6|0 zKw6*k;M?l1_6c(jxT(^Hi6fNCFW4{7Zl-v?2_khg3cpMlA!Wvju4_{Wrp*e#YDkmY zO33E)5orn0ohv;qf)+1y3gyNwk)b{lawS4_ba=ElMgkn=T-OJ*ItcKk1_Ma46`QGr zJZlw5Lm%DQc|Q_rb%2J>TE27ow?|x98Br^u!Z_EAKNe3k=O#1s3b;GkeBT zzHw-4f5?8G{UE#7cBOW+?1F8d*cRK)w(VnUX>-PAnawnt53EmHXIf9T{?OV|P%B6j z4B}t$2l+%k)atoaxz$%zZk8u4msn1*m}KG3o#(Q-)9+xb#ol*5_&@O${`kj#`=CCr zU%!4(a`l>S1w|<5#S07M<6Z8rh}VeK8lfyos1V1go4-a8>ePw}{b|^=7eZK?k4hYx zP#2qDADw-^y~{_OfNQmA-Ei~YQtz#5v(Bs1BRc!1@l3FE$&!2Z=0D!dh?_buDeWQ& z`kkx$ouZsdg5wmyJY)CrNOX)560s^mBvB`DuJWrlD^U{6Jewug*%R9deyGEnJFMlV z0jp*#ixjCf;n9*9U}8#_R4^j6>?|=0Su;(k&I$uV!y%^&ezAdq0HnB(TiDIw*2QG0 z3VnP8rKV7=QL2=)&}Kz-JMV{{n3xSq^}qFNA;!dl59m~&HS01T#s2l-k;$MFz302X z4@ym>+EiYUXLOXQ$Fp9{f`X2MxxCss?9>gv#XKe)2_m61TB$(0TirF=@J`prFoQu2 zD*77?GSPm9-uMuQC;k2Ewp36PPt5-sWJ`_rvyKupknGXP|u4~A`AY(iAp#DD|$ZxP|^h)twD zkljzH95qrLD~wTUL@@-Wt1Na#zk8BgRB{>(vC>0IdK^g0q`sa*-1J;r`6%#A-eDY} zUXt}T1z3h?sch`xNU;C@7q5g6^hUgR{=QRjV$9(*z^IrweUwtB1srQ+9;4|DR8=Te z3DeV^%wNJpRj7n8i9gW$a9(eV?lwo>X5O-@nj-yK56ozKgb{aO!1jA>5W;aHKeM|;<9gT#RY1!$RwY>F6voVKI0RS zB0{AV#-qB)>#ZH$_NuwBgh`lqzuJn8mP~v&zwkwIMv|2zR4GA;$Dj)C^s#Y=I4wq9 z7VC-sHme#`led_5-w{U_k663$k;R|;)k2j>9i!DKfL!_x@m-0%zz>Z>Jr?A(Y|25z zd=DAHza}}E=uk&4kPi`#3t9idZYyRZYN1pV6C=TxX9+9_m(ZxImPi5#g;tln_zFTk zf*{^$rO$};Q~N$PF) zwynkvO&N!vp#Wz4to-8`0UV)6*3)NCdXDz%7XvnhZNkbc=Gw3+YY{ArAqaNJ$VV}f zhV|5>A+R#zCN;5soDXk#OusSjfnOW+pBfP+NYsepg);UF_S@6+OBqXY{VLKQ5cIW! zcY3;%|7dhLdIhU0DLNu6zHEs;u5p5gIpQ1Rq+%0hbRMwh&#XRB!3n1FC)P+>`h*6q z#0YRp8?QI9ZAx`Uo-mT4K_UVSAwxu(%>(-Gsao9uN+?dBOqsYcer$WaI$EBfQbtBX z*@5ZXz`{&(AU2WM0v?0t2P7&QiJrY@v0)%%w(TG|>a+|bKh z)3o22J8OlF8k8ENQN_0wCz>T&(;$J&^41tXxaj6`s@v`~S=rxWsdLgoby%cWO`P1e z#ibW^Y=%*cgOCn*71JG7RLpQQws6-XLQy}IYhT}SGh6Q;*+H&QYNa9#rjg1xpOk-{tuH$m}yGVqX1=hF#AKZC2JtM*t^^*#)EE zsdp|ZG7)nn@D*~76!qA&ql{YYP6A*4BiYqy7mwSX#=V3F6S9PWUV)tJtOwuMn-AAi zu}ScwjmI;FL9iV<_{E)O*gf+0nkJD&h*VnklfKeFXYntS@s$qPln)>lU*+n&Npu+=U@M+=s3B^`1dobHpnTBin?rNv^ zMN}RTlSbuDnK^P*-Z@IDAlN-|#jgA#vU|s*(KOUSZ==MhKMJqKvDDZ~&+KxgnGNVf zF~vka^iW62YO4vPhGs3eHy7Icpe-SAmgvA%|M@blWEQ3bQmqDi2sO!3P<>p3x4?7Yjf3&`hYH3#*%d&Y!d0r}G0K=*Dlhyj|O9eAtX*k6OGTsXfF30Kd~)eC>IC#KGBe zj8T>7VKD#|Dnweq7&vyA(_3>;TAiYW03;DzzZr`C<~^yT)OX{8`Ei>Y_8xm>H%B}} zCKVxEs7g1cI|DIuy7&F1coalB0y&4A_BiccnyT#=w|{F^ z_mQPfb~V}X*mYZe7Cv)!!jDj{l5;uklHoGg`Kj|Z=Sb(SPPd&3og$pP9iKTKbzJ2* z$$OFCrbN-%Icdd%8zO(YTd}#T-rP9*J;*!NG3#~;D?gdwAzubO`t+eR?7JHgr z{+k!UDTmhO1mbMIxYj#fn`|v#_3|QR6%Ydj0AwDQJZRp+>{k9Ag2>C=Q)(ZbY|I4Z z$@JNR(;HiZe4kBUiZFVMDuO$i0+Hl`Q)-G=(2b-IhZ}{m4sHE6@0dgwg&Cwsp+uoj zh?)b`Qi^|8BFu5@ky4glIGwyA_<&!YyJYHg*J&StviEJB`2`9Ltr411Tu~**S2_T~ zLfV&II2f9divcXS5iVx{7Hh}(zMZaH060YMiH5RxAC*$H;5XCplzc+(Z=*(yURjP+ zN>5xgjl2!TbX5wVqET=@M9Y9PjLH)?MV=yG={^$7fwt5yWk*JO7`8;cr{eXckX+&9 zw0O*dMZ#I=Sz>zUe+imF+?rYrv`bkGPTvCN?kU@Teqq5*-k+bHU?{ucw`&CUwAcMQ zqr<>W<09OZ6NKV;HAcNLiPj#Pl3#d!4CH8XdfK3zy3==%jY1eD6U|C6jXR@kYE*AX zoT)*fksz)T8T)E#QF^Cr_~j+>WDY>~+tH7^RHt2^iZIbjhocxXH{;aQFfOL9$#ex% z3K%J&jf_m$kh6e#Uq47|yN?Vyo|+Z_7OIBdF=;S&!SK)e&$Fq0ggs~m$XZ~cq&kX) zH-({FN_NBM57}md_>?JM+zB~iKNqvW%Xfwt=-LJHG{H7|)?|wwDXUWFTQQlz{J|LU zwF0|V6VRMQUwz&@>heHK(s6i9ddV8J`9Ud}iw`RyNe#}grK{&mkB{z)>nnvQK@=rZ zqG>I5n$sMAbFgtLVq``t)#msONy(_+B!(UuhDz(}{;5%IIfqFLZtH&Dbcc|6e~%5Rx*l>K;)R zdLCil_z4Xy2e!P89#5u3YV^U)k7QGqRn4tHtv{_w19Zlxx}~I4B+?@v94hG8@U92z zJUTqZ$`ubL8-BnhnWxs%x#27HXSCaaDf*ociA%X7`i!F;ye7pBZ7))ZB+@_-u$6QA z+1`l9<6^_ z@=yvXdW0bK=sLT(TXQa8(r*GdQ3Q>CX#5+Ualhj4I7e4nC5_V`Q~mF!|Lfvi(*^cq zzWmuR&fd4W+96~2RP=yx5{)>p zJ8on3le`}^K7qrY0HE-~Nstm04*m4Ty^kCHklHgh;%1%0WZ3ZH@~ZdwVFL>fh z*@JZS)f=lGGsXa0jM6+fynSopF2sUe=jIQ!uxZC?;zEGJ`Hj9b$s3OS#0U)JOAns4 zy!mAsVj;WCVJtQOfDYC$u_hD_K$&2>Xc{7U>#O%=n&dy@b6UEHLvn4vKUZ0K{);*r zLNF8h(PF{+iU*6ofWfHUyS=>A+1h8ZISSNi74gQ%bu*fcg+0**lPDoiMPmYm==T&X zq#+3nitA4KbH{Ehi&Tndq5ow!USS)5XrB9PZ~#mmunB6+%n?|A^1L53ctOL|cAh^D zcJDJqs+DSKLaRnfbFOp!LO9lg7eSO@1C7BN*bI1p57+}mz4e9VizLuOC8VUY<>;Wi zozu|@NPeYTs5&f6Ju`Ab*P+h3NBbFSsw5N(RH9Sv^{mzqBXV4eVk z=y$x@&^VwzF4+F0RypnxVx}%6z47YDgtvGE3ZA0o9}dK;CJPMF@3?%#G&&s3cRKW> zZ^jO*0Q8e+uV4XX94wH$#%;cVN^tY%ci0Cd6upf#dANRC+Q#1O91?!IGX$fu01c=+?{?gvbTk4zrZmF`w(VTxQtVNf5=astY_l=E3gFaFuA* ztOR-xg>J>S@4RLJ$-!X?u?2D~%{N#hYEt=5uu{nN*XLJXC7kL6e~Z5Meqlc;GJ)=G z1LVR;i41dE#lnFnS%oFMXAfV{up7HJg$*g`q|Yn3Jc%8&BmcuU2XAhkP*Q|q`7A6L zR$-luDWgO5>-D7+pLBh&B6!K{_yu*Z@r23N@g>ctI*5lRuT&tl%n?dU4btbXsG%Vj zRp_ysIA_^_8);HBS&?E)$1!13T>D)Jba?ANl_e_q1r~;lNso_yUBhm)8o0mr<#RUd zZ!z_239nMB(X&QqS-YBw#KMz-o)d?BK>{Q6Yp`VF1W&=u)roP?&<xkxG(cgF)EiX7GD=BYvgpXulQvV)@0 z5A%-NB_G#%zKrh_rBZ5RK*<#ea-~)srqm3l573Xb#n(3#QX|b_uF}P@Jz&x+-=TPl z>yzAmhD_R_F|BdjMcRY(x+}laaJmCn!?P>lb&KY<_*Ef}N4KqwkHC(;Qk4+hG4W1z z#mzOH+jzBmd!JM~M{y#Ru@R4EyN(p?A216iTMWW&eI0)u(yZ|fgVz0+Ebhb(})u}T>p6VRW3p7VC|&e-tgGiOgqEHnRS~ren&u8jP4k!Uzc-g2OYk;3Jw9=Tc3<&={!XBdXwU zJsvbN`O%{%bmhqj max_length: + if current_part: + parts.append(current_part.rstrip()) + current_part = "" + current_part += line + '\n' + + # Добавляем последнюю часть + if current_part: + parts.append(current_part.rstrip()) + + self.logger.info(f"Сообщение разбито на {len(parts)} частей для Discord") + + # Отправляем каждую часть + for i, part in enumerate(parts, 1): + if len(parts) > 1: + part_content = f"[Часть {i}/{len(parts)}]\n\n{part}" + else: + part_content = part + await channel.send(part_content) + + # Небольшая задержка между частями + if i < len(parts): + await asyncio.sleep(1) + self.logger.info("Сообщение успешно отправлено в Discord") except Exception as e: self.logger.error(f"Ошибка отправки сообщения в Discord: {e}") @@ -142,7 +178,7 @@ class DiscordClient: await client.close() return - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI() diff --git a/install-service.sh b/install-service.sh new file mode 100755 index 0000000..a3facc5 --- /dev/null +++ b/install-service.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +# Установочный скрипт для Linux Gaming News Bot +# Создает systemd service и настраивает его для автозапуска + +set -e # Выход при любой ошибке + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Функции для цветного вывода +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Проверка запуска от root +if [ "$EUID" -ne 0 ]; then + print_error "Скрипт должен быть запущен с правами root (sudo)" + print_info "Используйте: sudo ./install-service.sh" + exit 1 +fi + +# Определение текущего пользователя (не root) +REAL_USER=$(who am i | awk '{print $1}') +if [ -z "$REAL_USER" ]; then + REAL_USER=$(logname 2>/dev/null || echo $SUDO_USER) +fi + +if [ -z "$REAL_USER" ]; then + print_error "Не удалось определить имя пользователя" + exit 1 +fi + +print_info "Установка сервиса для пользователя: $REAL_USER" + +# Определение директории проекта +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$SCRIPT_DIR" + +print_info "Директория проекта: $PROJECT_DIR" + +# Проверка существования файлов +if [ ! -f "$PROJECT_DIR/news-bot-modular.py" ]; then + print_error "Не найден файл news-bot-modular.py в $PROJECT_DIR" + exit 1 +fi + +if [ ! -f "$PROJECT_DIR/bot-news-linux-gaming.service" ]; then + print_error "Не найден файл bot-news-linux-gaming.service в $PROJECT_DIR" + exit 1 +fi + +# Проверка наличия keys.py +if [ ! -f "$PROJECT_DIR/keys.py" ]; then + print_warning "Не найден файл keys.py с настройками" + print_info "Создайте keys.py на основе keys_example.py перед запуском сервиса:" + print_info " cp keys_example.py keys.py" + print_info " nano keys.py # заполните реальными ключами" +fi + +# Создание временного файла сервиса с правильными путями +TEMP_SERVICE=$(mktemp) +sed "s|/home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming|$PROJECT_DIR|g" "$PROJECT_DIR/bot-news-linux-gaming.service" > "$TEMP_SERVICE" +sed -i "s|User=xpamych|User=$REAL_USER|g" "$TEMP_SERVICE" +sed -i "s|Group=xpamych|Group=$REAL_USER|g" "$TEMP_SERVICE" + +print_info "Копирование systemd unit файла..." +cp "$TEMP_SERVICE" /etc/systemd/system/bot-news-linux-gaming.service +rm "$TEMP_SERVICE" + +print_info "Установка прав доступа..." +chmod 644 /etc/systemd/system/bot-news-linux-gaming.service +chown root:root /etc/systemd/system/bot-news-linux-gaming.service + +print_info "Перезагрузка systemd..." +systemctl daemon-reload + +print_success "Сервис успешно установлен!" +print_info "" +print_info "Для управления сервисом используйте команды:" +print_info " sudo systemctl enable bot-news-linux-gaming # Включить автозапуск" +print_info " sudo systemctl start bot-news-linux-gaming # Запустить сервис" +print_info " sudo systemctl status bot-news-linux-gaming # Посмотреть статус" +print_info " sudo systemctl stop bot-news-linux-gaming # Остановить сервис" +print_info " sudo systemctl disable bot-news-linux-gaming # Отключить автозапуск" +print_info "" +print_info "Логи сервиса:" +print_info " sudo journalctl -u bot-news-linux-gaming -f # Следить за логами" +print_info " sudo journalctl -u bot-news-linux-gaming -n 50 # Последние 50 строк" +print_info "" + +# Предложение сразу включить и запустить +read -p "Включить автозапуск и запустить сервис сейчас? (y/N): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + print_info "Включение автозапуска..." + systemctl enable bot-news-linux-gaming + + print_info "Запуск сервиса..." + systemctl start bot-news-linux-gaming + + sleep 2 + + print_info "Статус сервиса:" + systemctl status bot-news-linux-gaming --no-pager + + print_success "Сервис запущен и добавлен в автозагрузку!" +else + print_info "Сервис установлен, но не запущен. Запустите его командой:" + print_info " sudo systemctl enable --now bot-news-linux-gaming" +fi \ No newline at end of file diff --git a/service-control.sh b/service-control.sh new file mode 100755 index 0000000..57764ed --- /dev/null +++ b/service-control.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +# Простой скрипт управления Linux Gaming News Bot + +SERVICE_NAME="bot-news-linux-gaming" + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Функция показа статуса +show_status() { + print_info "Статус сервиса $SERVICE_NAME:" + systemctl status $SERVICE_NAME --no-pager -l +} + +# Функция показа логов +show_logs() { + local lines=${1:-50} + print_info "Последние $lines строк логов:" + sudo journalctl -u $SERVICE_NAME -n $lines --no-pager +} + +# Функция следения за логами +follow_logs() { + print_info "Следование за логами (Ctrl+C для выхода):" + sudo journalctl -u $SERVICE_NAME -f +} + +# Функция показа помощи +show_help() { + echo "Управление Linux Gaming News Bot" + echo "" + echo "Использование: $0 [команда]" + echo "" + echo "Команды:" + echo " start - Запустить сервис" + echo " stop - Остановить сервис" + echo " restart - Перезапустить сервис" + echo " status - Показать статус сервиса" + echo " enable - Включить автозапуск" + echo " disable - Отключить автозапуск" + echo " logs - Показать последние логи" + echo " logs N - Показать последние N строк логов" + echo " follow - Следить за логами в реальном времени" + echo " install - Установить сервис (требует sudo)" + echo " uninstall - Удалить сервис (требует sudo)" + echo " help - Показать эту справку" + echo "" +} + +# Проверка существования сервиса +check_service_exists() { + if ! systemctl list-unit-files | grep -q "^$SERVICE_NAME.service"; then + print_error "Сервис $SERVICE_NAME не установлен" + print_info "Запустите: sudo ./install-service.sh" + exit 1 + fi +} + +# Основная логика +case "${1}" in + "start") + check_service_exists + print_info "Запуск сервиса..." + sudo systemctl start $SERVICE_NAME + show_status + ;; + "stop") + check_service_exists + print_info "Остановка сервиса..." + sudo systemctl stop $SERVICE_NAME + show_status + ;; + "restart") + check_service_exists + print_info "Перезапуск сервиса..." + sudo systemctl restart $SERVICE_NAME + show_status + ;; + "status") + check_service_exists + show_status + ;; + "enable") + check_service_exists + print_info "Включение автозапуска..." + sudo systemctl enable $SERVICE_NAME + print_success "Автозапуск включен" + ;; + "disable") + check_service_exists + print_info "Отключение автозапуска..." + sudo systemctl disable $SERVICE_NAME + print_success "Автозапуск отключен" + ;; + "logs") + check_service_exists + if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then + show_logs $2 + else + show_logs + fi + ;; + "follow") + check_service_exists + follow_logs + ;; + "install") + if [ ! -f "./install-service.sh" ]; then + print_error "Файл install-service.sh не найден" + exit 1 + fi + sudo ./install-service.sh + ;; + "uninstall") + if [ ! -f "./uninstall-service.sh" ]; then + print_error "Файл uninstall-service.sh не найден" + exit 1 + fi + sudo ./uninstall-service.sh + ;; + "help"|"--help"|"-h") + show_help + ;; + "") + print_warning "Команда не указана" + show_help + exit 1 + ;; + *) + print_error "Неизвестная команда: $1" + show_help + exit 1 + ;; +esac \ No newline at end of file diff --git a/telegram_client.py b/telegram_client.py index 7d435d8..aa09f02 100644 --- a/telegram_client.py +++ b/telegram_client.py @@ -82,37 +82,81 @@ class TelegramNewsClient: return None async def send_message(self, client, channel_username, content): - """Отправка сообщения в Telegram канал/топик с обработкой flood wait""" + """Отправка сообщения в Telegram канал/топик с обработкой flood wait и длинных сообщений""" try: # Получаем entity канала entity = await client.get_entity(channel_username) - while True: - try: - # Если указан topic_id, отправляем в топик - if self.config['topic_id']: - self.logger.debug(f"Отправка в топик {self.config['topic_id']}") - await client.send_message( - entity, - content, - reply_to=self.config['topic_id'] - ) - self.logger.info(f"Сообщение успешно отправлено в Telegram топик {self.config['topic_id']}") + # Telegram лимит: 4096 символов + max_length = 4096 + + # Если сообщение слишком длинное, разбиваем его + if len(content) > max_length: + self.logger.warning(f"Сообщение слишком длинное ({len(content)} символов), разбиваем на части") + + # Разбиваем по параграфам, чтобы не резать посередине слов + parts = [] + current_part = "" + + for line in content.split('\n'): + # Если добавление этой строки превысит лимит + if len(current_part + line + '\n') > max_length: + if current_part: + parts.append(current_part.rstrip()) + current_part = "" + current_part += line + '\n' + + # Добавляем последнюю часть + if current_part: + parts.append(current_part.rstrip()) + + self.logger.info(f"Сообщение разбито на {len(parts)} частей") + + # Отправляем каждую часть + for i, part in enumerate(parts, 1): + if len(parts) > 1: + part_content = f"[Часть {i}/{len(parts)}]\n\n{part}" else: - # Обычная отправка в канал - await client.send_message(entity, content) - self.logger.info("Сообщение успешно отправлено в Telegram канал") - break - except FloodWaitError as e: - self.logger.warning(f"Flood wait error: нужно подождать {e.seconds} секунд") - await asyncio.sleep(e.seconds) - except Exception as e: - self.logger.error(f"Ошибка отправки сообщения в Telegram: {e}") - break + part_content = part + + await self._send_single_message(client, entity, part_content) + + # Небольшая задержка между частями + if i < len(parts): + await asyncio.sleep(1) + else: + # Обычная отправка + await self._send_single_message(client, entity, content) + except Exception as e: self.logger.error(f"Ошибка получения entity канала '{channel_username}': {e}") self.logger.info("Убедитесь, что имя канала указано правильно и бот имеет доступ") + async def _send_single_message(self, client, entity, content): + """Отправка одного сообщения с обработкой flood wait""" + while True: + try: + # Если указан topic_id, отправляем в топик + if self.config['topic_id']: + self.logger.debug(f"Отправка в топик {self.config['topic_id']}") + await client.send_message( + entity, + content, + reply_to=self.config['topic_id'] + ) + self.logger.info(f"Сообщение успешно отправлено в Telegram топик {self.config['topic_id']}") + else: + # Обычная отправка в канал + await client.send_message(entity, content) + self.logger.info("Сообщение успешно отправлено в Telegram канал") + break + except FloodWaitError as e: + self.logger.warning(f"Flood wait error: нужно подождать {e.seconds} секунд") + await asyncio.sleep(e.seconds) + except Exception as e: + self.logger.error(f"Ошибка отправки сообщения в Telegram: {e}") + raise + async def check_and_publish_news(self, news_list): """Проверка и публикация новостей в Telegram""" self.logger.info("Начинаем проверку новостей для Telegram") @@ -151,7 +195,7 @@ class TelegramNewsClient: self.logger.info(f"Новости для публикации в Telegram: {list_for_public}") - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI() diff --git a/uninstall-service.sh b/uninstall-service.sh new file mode 100755 index 0000000..e1b0219 --- /dev/null +++ b/uninstall-service.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Скрипт удаления Linux Gaming News Bot systemd service + +set -e # Выход при любой ошибке + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Функции для цветного вывода +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Проверка запуска от root +if [ "$EUID" -ne 0 ]; then + print_error "Скрипт должен быть запущен с правами root (sudo)" + print_info "Используйте: sudo ./uninstall-service.sh" + exit 1 +fi + +SERVICE_NAME="bot-news-linux-gaming" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + +print_info "Удаление сервиса $SERVICE_NAME..." + +# Остановка сервиса если он запущен +if systemctl is-active --quiet $SERVICE_NAME; then + print_info "Остановка сервиса..." + systemctl stop $SERVICE_NAME +fi + +# Отключение автозапуска если включен +if systemctl is-enabled --quiet $SERVICE_NAME; then + print_info "Отключение автозапуска..." + systemctl disable $SERVICE_NAME +fi + +# Удаление файла сервиса +if [ -f "$SERVICE_FILE" ]; then + print_info "Удаление файла сервиса..." + rm "$SERVICE_FILE" +else + print_warning "Файл сервиса не найден: $SERVICE_FILE" +fi + +# Перезагрузка systemd +print_info "Перезагрузка systemd..." +systemctl daemon-reload + +print_success "Сервис $SERVICE_NAME успешно удален!" +print_info "Файлы проекта не затронуты и остались на месте." \ No newline at end of file diff --git a/vk_client.py b/vk_client.py index a767eb7..3eab634 100644 --- a/vk_client.py +++ b/vk_client.py @@ -155,7 +155,7 @@ class VKClient: self.logger.info(f"Новости для публикации в VK: {list_for_public}") - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI()