Telegram-bot for et personlig utvalg av artikler fra Habr

For spørsmål som "hvorfor?" det er en eldre artikkel - Natural Geektimes - gjør rommet renere.

Det er mange artikler, av subjektive grunner liker jeg ikke noen av dem, og noen, tvert imot, er det synd å hoppe over. Jeg ønsker å optimalisere denne prosessen og spare tid.

Artikkelen ovenfor foreslo en skripttilnærming i nettleseren, men jeg likte den ikke (selv om jeg har brukt den før) av følgende grunner:

  • For forskjellige nettlesere på datamaskinen/telefonen din, må du konfigurere den på nytt, hvis det er mulig.
  • Streng filtrering etter forfattere er ikke alltid praktisk.
  • Problemet med forfattere hvis artikler du ikke vil gå glipp av, selv om de publiseres en gang i året, er ikke løst.

Filtrering innebygd i nettstedet basert på artikkelvurderinger er ikke alltid praktisk, siden høyt spesialiserte artikler, til tross for deres verdi, kan få en ganske beskjeden vurdering.

I utgangspunktet ønsket jeg å generere en RSS-feed (eller til og med flere), og la bare interessante ting der. Men til slutt viste det seg at det ikke virket særlig praktisk å lese RSS: I alle fall, for å kommentere/stemme på en artikkel/legge den til i favorittene dine, må du gå gjennom nettleseren. Derfor skrev jeg en telegram-bot som sender interessante artikler til meg i en personlig melding. Telegram selv lager vakre forhåndsvisninger av dem, som, kombinert med informasjon om forfatteren/vurderingen/visningene, ser ganske informative ut.

Telegram-bot for et personlig utvalg av artikler fra Habr

Under snittet er detaljer som trekk ved arbeidet, skriveprosessen og tekniske løsninger.

Kort om boten

Oppbevaringssted: https://github.com/Kright/habrahabr_reader

Bot i telegram: https://t.me/HabraFilterBot

Brukeren angir en tilleggsvurdering for tagger og forfattere. Deretter brukes et filter på artiklene - artikkelens vurdering på Habré, forfatterens brukervurdering og gjennomsnittet for brukervurderinger etter tag legges sammen. Hvis mengden er større enn en brukerspesifisert terskel, passerer artikkelen filteret.

Et sidemål med å skrive en bot var å få moro og erfaring. I tillegg minnet jeg meg selv på det jevnlig Jeg er ikke Google, og derfor gjøres mange ting så enkelt og primitivt som mulig. Dette forhindret imidlertid ikke at prosessen med å skrive boten tok tre måneder.

Det var sommer ute

Juli var over, og jeg bestemte meg for å skrive en bot. Og ikke alene, men med en venn som mestret scala og ville skrive noe om det. Begynnelsen så lovende ut - koden ville bli kuttet av et team, oppgaven virket enkel og jeg trodde at om et par uker eller en måned ville boten være klar.

Til tross for at jeg selv har skrevet kode på berget fra tid til annen de siste årene, er det vanligvis ingen som ser eller ser på denne koden: kjæledyrprosjekter, testing av noen ideer, forbehandling av data, mestring av noen konsepter fra FP. Jeg var veldig interessert i hvordan det å skrive kode i et team ser ut, fordi kode på stein kan skrives på veldig forskjellige måter.

Hva kunne ha gått ? La oss imidlertid ikke forhaste oss.
Alt som skjer kan spores ved hjelp av commit-historikken.

En bekjent opprettet et depot 27. juli, men gjorde ikke noe annet, så jeg begynte å skrive kode.

30 juli

Kort: Jeg skrev en analyse av Habrs rss-feed.

  • com.github.pureconfig for å lese typesafe konfigurasjoner direkte inn i case-klasser (det viste seg å være veldig praktisk)
  • scala-xml for lesing av xml: siden jeg opprinnelig ønsket å skrive min egen implementering for rss-feeden, og rss-feeden er i xml-format, brukte jeg dette biblioteket for å analysere. Faktisk dukket også RSS-parsing opp.
  • scalatest for tester. Selv for små prosjekter sparer skriving av tester tid - for eksempel ved feilsøking av xml-parsing er det mye enklere å laste det ned til en fil, skrive tester og rette feil. Da en feil senere dukket opp med å analysere noe merkelig html med ugyldige utf-8-tegn, viste det seg å være mer praktisk å legge den i en fil og legge til en test.
  • skuespillere fra Akka. Objektivt sett var de ikke nødvendig i det hele tatt, men prosjektet var skrevet for moro skyld, jeg ville prøve dem. Som et resultat er jeg klar til å si at jeg likte det. Ideen om OOP kan sees på fra den andre siden - det er aktører som utveksler meldinger. Det som er mer interessant er at du kan (og bør) skrive kode på en slik måte at meldingen kanskje ikke kommer frem eller ikke blir behandlet (generelt sett, når kontoen kjører på én enkelt datamaskin, bør meldinger ikke gå tapt). Først klørte jeg meg i hodet og det var søppel i koden med skuespillere som abonnerte på hverandre, men til slutt klarte jeg å komme opp med en ganske enkel og elegant arkitektur. Koden inne i hver skuespiller kan betraktes som entrådet; når en skuespiller krasjer, starter accaen den på nytt - resultatet er et ganske feiltolerant system.

9 august

Jeg la til prosjektet scala-scrapper for å analysere html-sider fra Habr (for å trekke ut informasjon som artikkelvurdering, antall bokmerker osv.).

Og katter. De i berget.

Telegram-bot for et personlig utvalg av artikler fra Habr

Jeg leste så en bok om distribuerte databaser, jeg likte ideen om CRDT (Konfliktfri replikert datatype, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), så jeg la ut en typeklasse av en kommutativ semigruppe for informasjon om artikkelen om Habré.

Faktisk er ideen veldig enkel – vi har tellere som endrer seg monotont. Antall kampanjer vokser gradvis, det samme er antall plusser (så vel som antall minuser). Hvis jeg har to versjoner av informasjon om en artikkel, kan jeg "slå dem sammen til en" - tilstanden til telleren som er større anses som mer relevant.

En semigruppe betyr at to objekter med informasjon om en artikkel kan slås sammen til en. Kommutativ betyr at du kan slå sammen både A + B og B + A, resultatet avhenger ikke av rekkefølgen, og til slutt vil den nyeste versjonen forbli. Her er det forresten også assosiativitet.

For eksempel, som planlagt, ga rss etter parsing litt svekket informasjon om artikkelen – uten beregninger som antall visninger. En spesiell skuespiller tok så informasjon om artiklene og løp til html-sidene for å oppdatere den og slå den sammen med den gamle versjonen.

Generelt sett, som i akka, var det ikke behov for dette, du kunne ganske enkelt lagre updateDate for artikkelen og ta en nyere uten noen sammenslåinger, men eventyrets vei ledet meg.

12 august

Jeg begynte å føle meg friere, og bare for moro skyld gjorde jeg hver chat til en egen skuespiller. Teoretisk sett veier en skuespiller i seg selv omtrent 300 byte og de kan lages i millioner, så dette er en helt normal tilnærming. Det virker for meg at løsningen viste seg å være ganske interessant:

En aktør var en bro mellom telegramserveren og meldingssystemet i Akka. Han mottok ganske enkelt meldinger og sendte dem til ønsket chatteaktør. Chat-aktøren kunne sende noe tilbake som svar - og det ville bli sendt tilbake til telegrammet. Det som var veldig praktisk er at denne skuespilleren viste seg å være så enkel som mulig og inneholdt bare logikken for å svare på meldinger. Forresten, informasjon om nye artikler kom til hver chat, men igjen ser jeg ingen problemer i dette.

Generelt fungerte boten allerede, svarte på meldinger, lagret en liste over artikler sendt til brukeren, og jeg trodde allerede at boten nesten var klar. Jeg la sakte til små funksjoner som normalisering av forfatternavn og -tagger (erstatte "s.d f" med "s_d_f").

Det var bare én ting igjen lite men — staten ble ikke reddet noe sted.

Alt gikk galt

Du har kanskje lagt merke til at jeg skrev boten stort sett alene. Så den andre deltakeren ble involvert i utviklingen, og følgende endringer dukket opp i koden:

  • MongoDB så ut til å lagre tilstand. Samtidig ble loggene i prosjektet brutt, fordi Monga av en eller annen grunn begynte å spamme dem og noen mennesker bare slo dem av globalt.
  • Bridge-skuespilleren i Telegram ble forvandlet til det ugjenkjennelige og begynte å analysere meldinger selv.
  • Skuespillere for chatter ble nådeløst kuttet ut, og i stedet ble de erstattet av en skuespiller som gjemte all informasjon om alle chatter på en gang. For hvert nys gikk denne skuespilleren i trøbbel. Vel, ja, som når du oppdaterer informasjon om en artikkel, er det vanskelig å sende det til alle chatteaktører (vi er som Google, millioner av brukere venter på en million artikler i chatten for hver), men hver gang chatten oppdateres, det er normalt å gå inn i Monga. Som jeg skjønte mye senere, ble arbeidslogikken til chattene også fullstendig kuttet ut, og i stedet dukket det opp noe som ikke fungerte.
  • Det er ingen spor igjen av typeklassene.
  • Noen usunn logikk har dukket opp hos skuespillerne med deres abonnement på hverandre, noe som har ført til en rasetilstand.
  • Datastrukturer med typefelt Option[Int] omgjort til Int med magiske standardverdier som -1. Senere innså jeg at mongoDB lagrer json, og det er ingenting galt med å lagre det der Option vel, eller i det minste analysere -1 som Ingen, men på den tiden visste jeg ikke dette og tok mitt ord for det at "slik skal det være." Jeg skrev ikke den koden, og jeg gadd ikke å endre den foreløpig.
  • Jeg fant ut at min offentlige IP-adresse har en tendens til å endre seg, og hver gang måtte jeg legge den til hvitelisten til Mongo. Jeg lanserte boten lokalt, Monga var et sted på serverne til Monga som selskap.
  • Plutselig forsvant normaliseringen av tagger og meldingsformatering for telegrammer. (Hmm, hvorfor skulle det være det?)
  • Jeg likte at botens tilstand er lagret i en ekstern database, og når den startes på nytt, fortsetter den å fungere som om ingenting hadde skjedd. Dette var imidlertid det eneste pluss.

Den andre personen hadde ikke noe særlig hastverk, og alle disse endringene dukket opp i én stor haug allerede i begynnelsen av september. Jeg satte ikke umiddelbart pris på omfanget av den resulterende ødeleggelsen og begynte å forstå arbeidet med databasen, fordi... Jeg har aldri forholdt meg til dem før. Først senere innså jeg hvor mye arbeidskode som ble kuttet og hvor mange feil som ble lagt til i stedet.

September

Først tenkte jeg at det ville være nyttig å mestre Monga og gjøre det bra. Så begynte jeg sakte å forstå at organisering av kommunikasjon med databasen også er en kunst der du kan gjøre mange løp og bare gjøre feil. For eksempel hvis brukeren mottar to meldinger som /subscribe - og som svar på hver av dem vil vi opprette en oppføring i tabellen, fordi brukeren ikke abonnerer på tidspunktet for behandling av disse meldingene. Jeg har en mistanke om at kommunikasjon med Monga i sin nåværende form ikke er skrevet på den beste måten. For eksempel ble brukerens innstillinger opprettet i det øyeblikket han registrerte seg. Hvis han prøvde å endre dem før faktumet av abonnement... boten svarte ikke noe, fordi koden i skuespilleren gikk inn i databasen for innstillingene, fant den ikke og krasjet. På spørsmål om hvorfor ikke opprette innstillinger etter behov, fant jeg ut at det ikke er nødvendig å endre dem hvis brukeren ikke har abonnert... Meldingsfiltreringssystemet ble på en eller annen måte laget uklart, og selv etter en nærmere titt på koden kunne jeg forstår ikke om det var ment på denne måten i utgangspunktet eller om det er en feil der.

Det var ingen liste over artikler sendt til chatten; i stedet ble det foreslått at jeg skulle skrive dem selv. Dette overrasket meg – generelt sett var jeg ikke i mot å trekke alle mulige ting inn i prosjektet, men det ville vært logisk for den som tok inn disse tingene og skrudde dem fast. Men nei, den andre deltakeren så ut til å gi opp alt, men sa at listen i chatten visstnok var en dårlig løsning, og det var nødvendig å lage et skilt med hendelser som "en artikkel y ble sendt til bruker x." Så, hvis brukeren ba om å sende nye artikler, var det nødvendig å sende en forespørsel til databasen, som ville velge hendelser relatert til brukeren fra hendelsene, også få en liste over nye artikler, filtrere dem, sende dem til brukeren og kast hendelser om dette tilbake i databasen.

Den andre deltakeren ble ført bort et sted mot abstraksjoner, da boten vil motta ikke bare artikler fra Habr og ikke bare sendes til telegram.

Jeg implementerte på en eller annen måte hendelser i form av et eget skilt for andre halvdel av september. Det er ikke optimalt, men boten begynte i det minste å fungere og begynte å sende meg artikler igjen, og jeg fant sakte ut hva som skjedde i koden.

Nå kan du gå tilbake til begynnelsen og huske at depotet ikke opprinnelig ble opprettet av meg. Hva kunne ha gått slik? Min pull-forespørsel ble avvist. Det viste seg at jeg hadde redneck-kode, at jeg ikke visste hvordan jeg skulle jobbe i et team, og jeg måtte fikse feil i gjeldende implementeringskurve, og ikke avgrense den til en brukbar tilstand.

Jeg ble opprørt og så på forpliktelseshistorikken og mengden kode som ble skrevet. Jeg så på øyeblikk som opprinnelig ble skrevet godt, og som deretter ble brutt tilbake...

F*rk det

Jeg husket artikkelen Du er ikke Google.

Jeg tenkte at ingen egentlig trenger en idé uten implementering. Jeg tenkte at jeg vil ha en fungerende bot, som vil fungere i én enkelt kopi på én enkelt datamaskin som et enkelt java-program. Jeg vet at boten min vil fungere i flere måneder uten omstart, siden jeg allerede har skrevet slike roboter tidligere. Hvis den plutselig faller og ikke sender brukeren en annen artikkel, vil ikke himmelen falle til bakken og ingenting katastrofalt vil skje.

Hvorfor trenger jeg Docker, mongoDB og annen godsekult av "seriøs" programvare hvis koden rett og slett ikke fungerer eller fungerer skjevt?

Jeg splittet prosjektet og gjorde alt som jeg ville.

Telegram-bot for et personlig utvalg av artikler fra Habr

Omtrent samtidig byttet jeg jobb og fritiden ble sårt mangelfull. Om morgenen våknet jeg rett på toget, om kvelden kom jeg sent tilbake og ville ikke lenger gjøre noe. Jeg gjorde ingenting på en stund, så overmannet ønsket om å fullføre boten meg, og jeg begynte sakte å skrive om koden mens jeg kjørte til jobb om morgenen. Jeg vil ikke si at det var produktivt: Å sitte på et ristende tog med en bærbar datamaskin på fanget og se på stabeloverløp fra telefonen er ikke veldig praktisk. Tiden brukt på å skrive kode fløy imidlertid helt ubemerket forbi, og prosjektet begynte sakte å bevege seg mot en fungerende tilstand.

Et sted i bakhodet var det en orm av tvil som ønsket å bruke mongoDB, men jeg tenkte at i tillegg til fordelene med "pålitelig" tilstandslagring, var det merkbare ulemper:

  • Databasen blir et annet feilpunkt.
  • Koden blir mer kompleks, og det vil ta meg lengre tid å skrive den.
  • Koden blir treg og ineffektiv; i stedet for å endre et objekt i minnet, sendes endringene til databasen og trekkes om nødvendig tilbake.
  • Det er begrensninger på typen lagring av hendelser i en egen tabell, som er knyttet til databasens særegenheter.
  • Prøveversjonen av Monga har noen begrensninger, og hvis du støter på dem, må du starte og konfigurere Monga på noe.

Jeg kuttet ut mongaen, nå er botens tilstand ganske enkelt lagret i programmets minne og fra tid til annen lagret i en fil i form av json. Kanskje vil de i kommentarfeltet skrive at jeg tar feil, at det er her databasen skal brukes osv. Men dette er mitt prosjekt, tilnærmingen med filen er så enkel som mulig og den fungerer på en transparent måte.

Kastet ut magiske verdier som -1 og returnerte normale Option, lagt til lagring av en hash-tabell med sendte artikler tilbake til objektet med chat-informasjon. Lagt til sletting av informasjon om artikler eldre enn fem dager, for ikke å lagre alt. Jeg brakte logging til en fungerende tilstand - logger skrives i rimelige mengder til både filen og konsollen. Lagt til flere admin-kommandoer som lagring av tilstand eller innhenting av statistikk som antall brukere og artikler.

Rettet en haug med små ting: for eksempel for artikler er antall visninger, liker, misliker og kommentarer på tidspunktet for passering av brukerens filter nå indikert. Generelt er det overraskende hvor mange små ting som måtte korrigeres. Jeg førte en liste, noterte alle "uregelmessighetene" der og rettet dem så langt det var mulig.

For eksempel la jeg til muligheten til å angi alle innstillinger direkte i én melding:

/subscribe
/rating +20
/author a -30
/author s -20
/author p +9000
/tag scala 20
/tag akka 50

Og et annet lag /settings viser dem nøyaktig i dette skjemaet, kan du ta teksten fra den og sende alle innstillingene til en venn.
Det virker som en liten ting, men det er dusinvis av lignende nyanser.

Implementert artikkelfiltrering i form av en enkel lineær modell - brukeren kan sette en tilleggsvurdering for forfattere og tagger, samt en terskelverdi. Hvis summen av forfatterens vurdering, gjennomsnittlig vurdering for tagger og den faktiske vurderingen av artikkelen er større enn terskelverdien, vises artikkelen til brukeren. Du kan enten be boten om artikler med kommandoen /new, eller abonnere på boten og den vil sende artikler i en personlig melding når som helst på dagen.

Generelt sett hadde jeg en idé for hver artikkel å trekke ut flere funksjoner (huber, antall kommentarer, bokmerker, dynamikk i vurderingsendringer, mengde tekst, bilder og kode i artikkelen, nøkkelord), og vise brukeren en ok/ ikke ok stemme under hver artikkel og trene en modell for hver bruker, men jeg var for lat.

I tillegg vil ikke logikken i arbeidet være så åpenbar. Nå kan jeg manuelt angi en vurdering på +9000 for patientZero, og med en terskelvurdering på +20 vil jeg være garantert å motta alle artiklene hans (med mindre jeg selvfølgelig setter -100500 for noen tagger).

Den endelige arkitekturen viste seg å være ganske enkel:

  1. En skuespiller som lagrer statusen til alle chatter og artikler. Den laster inn tilstanden fra en fil på disken og lagrer den fra tid til annen, hver gang til en ny fil.
  2. En skuespiller som besøker RSS-feeden fra tid til annen, lærer om nye artikler, ser på lenkene, analyserer og sender disse artiklene til den første skuespilleren. I tillegg ber den noen ganger om en liste over artikler fra den første skuespilleren, velger de som ikke er eldre enn tre dager, men som ikke har blitt oppdatert på lenge, og oppdaterer dem.
  3. En skuespiller som kommuniserer med et telegram. Jeg brakte fortsatt meldingen parsing fullstendig her. På en minnelig måte vil jeg dele den i to - slik at den ene analyserer innkommende meldinger, og den andre tar for seg transportproblemer som å sende usendte meldinger på nytt. Nå er det ingen re-sending, og en melding som ikke kom på grunn av en feil vil rett og slett gå tapt (med mindre det er notert i loggene), men så langt har ikke dette skapt noen problemer. Kanskje det vil oppstå problemer hvis en haug med mennesker abonnerer på boten og jeg når grensen for å sende meldinger).

Det jeg likte er at takket være akka, påvirker fall av skuespillere 2 og 3 generelt ikke ytelsen til boten. Kanskje noen artikler ikke blir oppdatert i tide eller noen meldinger når ikke telegrammet, men kontoen starter skuespilleren på nytt og alt fortsetter å fungere. Jeg lagrer informasjonen om at artikkelen vises til brukeren først når telegramaktøren svarer at han har levert meldingen. Det verste som truer meg er å sende meldingen flere ganger (hvis den blir levert, men bekreftelsen på en eller annen måte går tapt). I prinsippet, hvis den første skuespilleren ikke lagret staten i seg selv, men kommuniserte med en database, kunne han også falle umerkelig og komme tilbake til livet. Jeg kunne også prøve akka-utholdenhet for å gjenopprette aktørenes tilstand, men den nåværende implementeringen passer meg med sin enkelhet. Det er ikke det at koden min krasjet ofte – tvert imot, jeg har lagt ganske mye arbeid i å gjøre det umulig. Men shit happens, og muligheten til å dele opp programmet i isolerte deler-skuespillere virket veldig praktisk og praktisk for meg.

Jeg la til sirkel-ci slik at hvis koden går i stykker, vil du umiddelbart finne ut om det. I det minste betyr det at koden har sluttet å kompilere. I utgangspunktet ønsket jeg å legge til travis, men det viste bare prosjektene mine uten gafler. Generelt kan begge disse tingene brukes fritt i åpne depoter.

Resultater av

Det er allerede november. Boten er skrevet, jeg har brukt den de siste to ukene og jeg likte den. Hvis du har ideer til forbedringer, skriv. Jeg ser ikke poenget med å tjene penger på det - la det bare fungere og send interessante artikler.

Bot link: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Små konklusjoner:

  • Selv et lite prosjekt kan ta mye tid.
  • Du er ikke Google. Det nytter ikke å skyte spurver fra en kanon. En enkel løsning kan fungere like bra.
  • Kjæledyrprosjekter er veldig gode for å eksperimentere med ny teknologi.
  • Telegram-roboter er skrevet ganske enkelt. Hvis det ikke var for "teamarbeid" og eksperimenter med teknologi, ville boten blitt skrevet om en uke eller to.
  • Skuespillermodellen er en interessant ting som går bra med multi-threading og feiltolerant kode.
  • Jeg tror jeg fikk en smak av hvorfor åpen kildekode-fellesskapet elsker gafler.
  • Databaser er gode fordi applikasjonstilstanden ikke lenger er avhengig av applikasjonskrasj/omstart, men å jobbe med en database kompliserer koden og legger begrensninger på datastrukturen.

Kilde: www.habr.com

Legg til en kommentar