Bot di Telegram per una selezione personalizzata di articoli di Habr

Per domande come "perché?" c'è un articolo più vecchio - Natural Geektimes: rendere lo spazio più pulito.

Ci sono molti articoli, per motivi soggettivi alcuni non mi piacciono, altri invece è un peccato saltarli. Vorrei ottimizzare questo processo e risparmiare tempo.

L'articolo precedente suggeriva un approccio di scripting nel browser, ma non mi è piaciuto molto (anche se l'ho già usato in precedenza) per i seguenti motivi:

  • Per browser diversi sul tuo computer/telefono, devi configurarlo nuovamente, se possibile.
  • Il filtraggio rigoroso per autore non è sempre conveniente.
  • Il problema con gli autori di cui non vuoi perderti gli articoli, anche se pubblicati una volta all’anno, non è stato risolto.

Il filtraggio integrato nel sito in base alle valutazioni degli articoli non è sempre conveniente, poiché articoli altamente specializzati, nonostante il loro valore, possono ricevere una valutazione piuttosto modesta.

Inizialmente, volevo generare un feed RSS (o anche più), lasciando lì solo le cose interessanti. Ma alla fine si è scoperto che leggere gli RSS non sembrava molto comodo: in ogni caso, per commentare/votare un articolo/aggiungerlo ai preferiti bisogna passare attraverso il browser. Ecco perché ho creato un bot di Telegram che mi invia articoli interessanti in un messaggio personale. Telegram stesso ne ricava bellissime anteprime che, combinate con le informazioni sull'autore/valutazione/visualizzazioni, sembrano piuttosto istruttive.

Bot di Telegram per una selezione personalizzata di articoli di Habr

Sotto il taglio ci sono dettagli come le caratteristiche dell'opera, il processo di scrittura e le soluzioni tecniche.

Brevemente sul bot

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

Bot in telegramma: https://t.me/HabraFilterBot

L'utente imposta una valutazione aggiuntiva per tag e autori. Successivamente, viene applicato un filtro agli articoli: vengono sommate la valutazione dell'articolo su Habré, la valutazione dell'utente dell'autore e la media delle valutazioni degli utenti per tag. Se l'importo è maggiore della soglia specificata dall'utente, l'articolo supera il filtro.

Un obiettivo secondario nello scrivere un bot era acquisire divertimento ed esperienza. Inoltre, me lo ricordavo regolarmente Non sono Google, e quindi molte cose vengono fatte nel modo più semplice e persino primitivo possibile. Tuttavia, ciò non ha impedito che il processo di scrittura del bot durasse tre mesi.

Fuori era estate

Luglio stava finendo e ho deciso di scrivere un bot. E non da solo, ma con un amico che stava imparando a scalare e voleva scriverci qualcosa. L'inizio sembrava promettente: il codice sarebbe stato tagliato da un team, il compito sembrava facile e pensavo che nel giro di un paio di settimane o un mese il bot sarebbe stato pronto.

Nonostante il fatto che io stesso abbia scritto codice sulla roccia di tanto in tanto negli ultimi anni, nessuno di solito vede o guarda questo codice: progetti per animali domestici, testare alcune idee, preelaborare dati, padroneggiare alcuni concetti da FP. Ero davvero interessato a come sarebbe scrivere codice in un team, perché il codice su rock può essere scritto in modi molto diversi.

Cosa sarebbe potuto succedere così? Tuttavia, non affrettiamo le cose.
Tutto ciò che accade può essere monitorato utilizzando la cronologia dei commit.

Un conoscente ha creato un repository il 27 luglio, ma non ha fatto altro, quindi ho iniziato a scrivere codice.

30 luglio

In breve: ho scritto un'analisi del feed RSS di Habr.

  • com.github.pureconfig per leggere le configurazioni typesafe direttamente nelle classi case (si è rivelato molto conveniente)
  • scala-xml per leggere xml: poiché inizialmente volevo scrivere la mia implementazione per il feed RSS e il feed RSS è in formato xml, ho utilizzato questa libreria per l'analisi. In realtà, è apparsa anche l'analisi RSS.
  • scalatest per i test. Anche per progetti piccoli, scrivere test fa risparmiare tempo: ad esempio, durante il debug dell'analisi xml, è molto più semplice scaricarlo in un file, scrivere test e correggere errori. Quando in seguito è apparso un bug durante l'analisi di uno strano codice HTML con caratteri utf-8 non validi, si è rivelato più conveniente inserirlo in un file e aggiungere un test.
  • attori di Akka. Oggettivamente non servivano affatto, ma il progetto è stato scritto per divertimento, volevo provarli. Di conseguenza, sono pronto a dire che mi è piaciuto. L'idea di OOP può essere vista dall'altra parte: ci sono attori che si scambiano messaggi. La cosa più interessante è che puoi (e dovresti) scrivere il codice in modo tale che il messaggio potrebbe non arrivare o non essere elaborato (in generale, quando l'account è in esecuzione su un singolo computer, i messaggi non dovrebbero andare persi). All'inizio mi stavo grattando la testa e c'era spazzatura nel codice con gli attori che si iscrivevano a vicenda, ma alla fine sono riuscito a trovare un'architettura piuttosto semplice ed elegante. Il codice all'interno di ogni attore può essere considerato a thread singolo; quando un attore va in crash, l'acca lo riavvia: il risultato è un sistema abbastanza tollerante ai guasti.

9 agosto

Ho aggiunto al progetto scala-scrapper per analizzare le pagine html da Habr (per estrarre informazioni come valutazione dell'articolo, numero di segnalibri, ecc.).

E gatti. Quelli nella roccia.

Bot di Telegram per una selezione personalizzata di articoli di Habr

Ho poi letto un libro sui database distribuiti, mi è piaciuta l'idea del CRDT (Conflict-free replicated data type, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), quindi ho pubblicato una classe di tipo di un semigruppo commutativo per informazioni sull'articolo su Habré.

In effetti, l'idea è molto semplice: abbiamo contatori che cambiano in modo monotono. Il numero di promozioni sta gradualmente crescendo, così come il numero di vantaggi (così come il numero di svantaggi). Se ho due versioni delle informazioni su un articolo, posso "unirle in una sola": lo stato del contatore più grande è considerato più rilevante.

Un semigruppo significa che due oggetti con informazioni su un articolo possono essere uniti in uno solo. Commutativo significa che puoi unire sia A + B che B + A, il risultato non dipende dall'ordine e alla fine rimarrà la versione più recente. A proposito, anche qui c'è associatività.

Ad esempio, come previsto, gli RSS dopo l'analisi hanno fornito informazioni leggermente indebolite sull'articolo, senza parametri come il numero di visualizzazioni. Un attore speciale ha quindi preso le informazioni sugli articoli ed è corso alle pagine html per aggiornarle e unirle alla vecchia versione.

In generale, come in akka, non ce n'era bisogno, potevi semplicemente memorizzare updateDate per l'articolo e prenderne uno più recente senza alcuna fusione, ma la strada dell'avventura mi ha portato.

12 agosto

Ho cominciato a sentirmi più libero e, tanto per divertirmi, ho fatto di ogni chiacchierata un attore a parte. Teoricamente, un attore pesa circa 300 byte e se ne possono creare milioni, quindi questo è un approccio del tutto normale. Mi sembra che la soluzione si sia rivelata piuttosto interessante:

Un attore era un ponte tra il server dei telegrammi e il sistema di messaggi ad Akka. Ha semplicemente ricevuto i messaggi e li ha inviati all'attore della chat desiderato. L'attore della chat potrebbe inviare qualcosa in risposta e verrebbe rimandato al telegramma. Ciò che è stato molto conveniente è che questo attore si è rivelato il più semplice possibile e conteneva solo la logica per rispondere ai messaggi. A proposito, in ogni chat sono arrivate informazioni sui nuovi articoli, ma ancora una volta non vedo alcun problema in questo.

In generale il bot era già in funzione, rispondeva ai messaggi, memorizzava l'elenco degli articoli inviati all'utente, e pensavo già che il bot fosse quasi pronto. Ho aggiunto lentamente piccole funzionalità come la normalizzazione dei nomi e dei tag degli autori (sostituendo "sd f" con "s_d_f").

Rimaneva solo una cosa piccolo ma — lo Stato non è stato salvato da nessuna parte.

Tutto è andato storto

Potresti aver notato che ho scritto il bot principalmente da solo. Quindi, il secondo partecipante è stato coinvolto nello sviluppo e nel codice sono apparse le seguenti modifiche:

  • MongoDB sembrava memorizzare lo stato. Allo stesso tempo, i log del progetto sono stati danneggiati, perché per qualche motivo Monga ha iniziato a inviarli tramite spam e alcune persone li hanno semplicemente disattivati ​​a livello globale.
  • L'attore del bridge in Telegram è stato trasformato in modo irriconoscibile e ha iniziato ad analizzare i messaggi da solo.
  • Gli attori delle chat sono stati eliminati senza pietà e invece sono stati sostituiti da un attore che nascondeva tutte le informazioni su tutte le chat contemporaneamente. Per ogni starnuto, questo attore è finito nei guai. Ebbene sì, come quando si aggiornano le informazioni su un articolo, inviarle a tutti gli attori della chat è difficile (siamo come Google, milioni di utenti aspettano un milione di articoli nella chat per ciascuno), ma ogni volta che la chat viene aggiornata, è normale andare a Monga. Come mi resi conto molto tempo dopo, anche la logica di funzionamento delle chat era stata completamente eliminata e al suo posto apparve qualcosa che non funzionava.
  • Non è rimasta traccia delle classi di tipo.
  • Una certa logica malsana è apparsa negli attori con le loro iscrizioni reciproche, portando a una condizione di razza.
  • Strutture dati con campi di tipo Option[Int] trasformato in Int con valori predefiniti magici come -1. Più tardi mi sono reso conto che mongoDB memorizza json e non c'è niente di sbagliato nel memorizzarlo lì Option beh, o almeno analizzare -1 come None, ma a quel tempo non lo sapevo e mi credevo sulla parola che "è così che dovrebbe essere". Non ho scritto quel codice e per il momento non mi sono preoccupato di cambiarlo.
  • Ho scoperto che il mio indirizzo IP pubblico tende a cambiare, e ogni volta ho dovuto aggiungerlo alla whitelist di Mongo. Ho lanciato il bot localmente, Monga era da qualche parte sui server di Monga come azienda.
  • All'improvviso è scomparsa la normalizzazione dei tag e la formattazione dei messaggi per i telegrammi. (Hmm, perché dovrebbe essere?)
  • Mi è piaciuto il fatto che lo stato del bot venga archiviato in un database esterno e, una volta riavviato, continui a funzionare come se nulla fosse accaduto. Tuttavia, questo è stato l'unico vantaggio.

La seconda persona non aveva particolare fretta e tutti questi cambiamenti sono apparsi in un unico grande mucchio già all'inizio di settembre. Non ho apprezzato immediatamente la portata della distruzione risultante e ho iniziato a capire il lavoro del database, perché... Non li ho mai affrontati prima. Solo più tardi mi resi conto di quanto codice funzionante fosse stato tagliato e di quanti bug fossero stati aggiunti al suo posto.

Settembre

All'inizio pensavo che sarebbe stato utile padroneggiare Monga e farlo bene. Poi ho iniziato lentamente a capire che anche organizzare la comunicazione con il database è un'arte in cui puoi fare molte gare e semplicemente commettere errori. Ad esempio, se l'utente riceve due messaggi simili /subscribe - e in risposta a ciascuno creeremo una voce nella tabella, perché al momento dell'elaborazione di tali messaggi l'utente non è iscritto. Ho il sospetto che la comunicazione con Monga nella sua forma attuale non sia scritta nel migliore dei modi. Ad esempio, le impostazioni dell'utente sono state create nel momento in cui si è registrato. Se ha provato a cambiarli prima dell'iscrizione... il bot non ha risposto nulla, perché il codice nell'attore è entrato nel database delle impostazioni, non l'ha trovato e si è bloccato. Quando mi è stato chiesto perché non creare le impostazioni secondo necessità, ho appreso che non è necessario modificarle se l'utente non è iscritto... Il sistema di filtraggio dei messaggi è stato realizzato in modo non ovvio e anche dopo uno sguardo attento al codice ho potuto non capisco se inizialmente era previsto in questo modo o se c'è un errore lì.

Non c'era un elenco di articoli inviati alla chat; mi è stato invece suggerito di scriverli io stesso. Questo mi ha sorpreso: in generale, non ero contrario a trascinare ogni sorta di cose nel progetto, ma sarebbe logico per chi ha portato queste cose e le ha rovinate. Invece no, il secondo partecipante sembrava rinunciare a tutto, ma ha detto che la lista all'interno della chat era presumibilmente una pessima soluzione, ed era necessario apporre un cartello con eventi come "un articolo y è stato inviato all'utente x". Quindi, se l'utente richiedeva di inviare nuovi articoli, era necessario inviare una richiesta al database, che avrebbe selezionato gli eventi relativi all'utente dagli eventi, avrebbe anche ottenuto un elenco di nuovi articoli, li avrebbe filtrati, li avrebbe inviati all'utente e reinserire gli eventi al riguardo nel database.

Il secondo partecipante è stato portato da qualche parte verso le astrazioni, quando il bot riceverà non solo articoli da Habr e verrà inviato non solo a Telegram.

In qualche modo ho implementato gli eventi sotto forma di un segno separato per la seconda metà di settembre. Non è ottimale, ma almeno il bot ha iniziato a funzionare e ha ricominciato a inviarmi articoli, e lentamente ho capito cosa stava succedendo nel codice.

Ora puoi tornare all'inizio e ricordare che il repository non è stato originariamente creato da me. Cosa potrebbe essere andato così? La mia richiesta di pull è stata respinta. Si è scoperto che avevo un codice redneck, che non sapevo come lavorare in una squadra e dovevo correggere i bug nell'attuale curva di implementazione e non perfezionarla in uno stato utilizzabile.

Mi sono arrabbiato e ho guardato la cronologia dei commit e la quantità di codice scritto. Ho guardato i momenti che originariamente erano scritti bene, e poi sono stati interrotti...

Fan*ulo

Mi sono ricordato dell'articolo Non sei Google.

Pensavo che nessuno avesse davvero bisogno di un'idea senza implementazione. Ho pensato di voler avere un bot funzionante, che funzionerà in un'unica copia su un singolo computer come un semplice programma Java. So che il mio bot funzionerà per mesi senza riavvii, poiché ho già scritto bot di questo tipo in passato. Se cade all'improvviso e non invia all'utente un altro articolo, il cielo non cadrà a terra e non accadrà nulla di catastrofico.

Perché ho bisogno di Docker, mongoDB e altri carichi di software "serio" se il codice semplicemente non funziona o funziona in modo storto?

Ho biforcato il progetto e ho fatto tutto come volevo.

Bot di Telegram per una selezione personalizzata di articoli di Habr

Più o meno nello stesso periodo ho cambiato lavoro e il tempo libero è diventato gravemente carente. La mattina mi svegliavo proprio sul treno, la sera tornavo tardi e non avevo più voglia di fare niente. Non ho fatto nulla per un po', poi il desiderio di finire il bot mi ha sopraffatto e ho cominciato a riscrivere lentamente il codice mentre andavo al lavoro la mattina. Non dirò che sia stato produttivo: sedersi su un treno tremante con un laptop in grembo e guardare lo stack overflow dal telefono non è molto conveniente. Tuttavia, il tempo trascorso a scrivere il codice è passato completamente inosservato e il progetto ha iniziato a muoversi lentamente verso uno stato funzionante.

Da qualche parte nella mia mente c'era un dubbio che voleva usare mongoDB, ma pensavo che oltre ai vantaggi dell'archiviazione dello stato "affidabile", ci fossero notevoli svantaggi:

  • Il database diventa un altro punto di errore.
  • Il codice sta diventando più complesso e mi ci vorrà più tempo per scriverlo.
  • Il codice diventa lento e inefficiente; invece di modificare un oggetto in memoria, le modifiche vengono inviate al database e, se necessario, ritirate.
  • Esistono restrizioni sul tipo di archiviazione degli eventi in una tabella separata, legate alle peculiarità del database.
  • La versione di prova di Monga presenta alcune limitazioni e, se le incontri, dovrai avviare e configurare Monga su qualcosa.

Ho tagliato il monga, ora lo stato del bot viene semplicemente archiviato nella memoria del programma e di volta in volta salvato in un file sotto forma di json. Forse nei commenti scriveranno che mi sbaglio, che è qui che dovrebbe essere utilizzato il database, ecc. Ma questo è il mio progetto, l'approccio con il file è il più semplice possibile e funziona in maniera trasparente.

Ha eliminato valori magici come -1 e ha restituito valori normali Option, aggiunta la memorizzazione di una tabella hash con gli articoli inviati all'oggetto con le informazioni sulla chat. Aggiunta la cancellazione delle informazioni sugli articoli più vecchi di cinque giorni, in modo da non archiviare tutto. Ho portato la registrazione a uno stato funzionante: i registri vengono scritti in quantità ragionevoli sia sul file che sulla console. Aggiunti diversi comandi di amministrazione come il salvataggio dello stato o l'ottenimento di statistiche come il numero di utenti e articoli.

Risolte una serie di piccole cose: ad esempio, per gli articoli ora viene indicato il numero di visualizzazioni, Mi piace, Non mi piace e commenti al momento del passaggio del filtro dell'utente. In generale, è sorprendente quante piccole cose abbiano dovuto essere corrette. Ho tenuto un elenco, ho annotato tutte le "irregolarità" e le ho corrette per quanto possibile.

Ad esempio, ho aggiunto la possibilità di configurare tutte le impostazioni direttamente in un unico messaggio:

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

E un'altra squadra /settings li visualizza esattamente in questo modulo, puoi prendere il testo da esso e inviare tutte le impostazioni a un amico.
Sembra una cosa da poco, ma ci sono decine di sfumature simili.

Filtraggio degli articoli implementato sotto forma di un semplice modello lineare: l'utente può impostare una valutazione aggiuntiva per autori e tag, nonché un valore di soglia. Se la somma della valutazione dell'autore, della valutazione media per i tag e della valutazione effettiva dell'articolo è maggiore del valore soglia, l'articolo verrà mostrato all'utente. Puoi chiedere articoli al bot con il comando /new, oppure iscriverti al bot e invierà articoli in un messaggio personale in qualsiasi momento della giornata.

In generale, avevo un'idea per ogni articolo per estrarre più funzionalità (hub, numero di commenti, segnalibri, dinamica dei cambiamenti di valutazione, quantità di testo, immagini e codice nell'articolo, parole chiave) e mostrare all'utente un ok/ non ok vota sotto ogni articolo e forma un modello per ogni utente, ma ero troppo pigro.

Inoltre, la logica del lavoro non sarà così ovvia. Ora posso impostare manualmente una valutazione di +9000 per pazienteZero e con una soglia di valutazione di +20 mi sarà garantito di ricevere tutti i suoi articoli (a meno che, ovviamente, non imposti -100500 per alcuni tag).

L'architettura finale si è rivelata piuttosto semplice:

  1. Un attore che memorizza lo stato di tutte le chat e gli articoli. Carica il suo stato da un file sul disco e lo salva di tanto in tanto, ogni volta in un nuovo file.
  2. Un attore che visita di tanto in tanto il feed RSS, viene a conoscenza di nuovi articoli, guarda i collegamenti, analizza e invia questi articoli al primo attore. Inoltre, a volte richiede un elenco di articoli al primo attore, seleziona quelli che non sono più vecchi di tre giorni, ma non sono stati aggiornati da molto tempo e li aggiorna.
  3. Un attore che comunica con un telegramma. Ho comunque portato qui il messaggio analizzandolo completamente. In modo amichevole, vorrei dividerlo in due, in modo che uno analizzi i messaggi in arrivo e il secondo si occupi dei problemi di trasporto come il reinvio di messaggi non inviati. Ora non è possibile inviare nuovamente e un messaggio che non è arrivato a causa di un errore andrà semplicemente perso (a meno che non sia annotato nei registri), ma finora ciò non ha causato alcun problema. Forse sorgeranno problemi se un gruppo di persone si iscrive al bot e io raggiungo il limite per l'invio di messaggi).

Quello che mi è piaciuto è che grazie ad akka, le cadute degli attori 2 e 3 generalmente non influiscono sulla performance del bot. Magari qualche articolo non viene aggiornato in tempo oppure qualche messaggio non arriva a Telegram, ma l'account riavvia l'attore e tutto continua a funzionare. Salvo l'informazione che l'articolo viene mostrato all'utente solo quando l'attore di Telegram risponde di aver consegnato con successo il messaggio. La cosa peggiore che mi minaccia è inviare il messaggio più volte (se viene consegnato, ma la conferma viene in qualche modo persa). In linea di principio, se il primo attore non memorizzasse lo stato dentro di sé, ma comunicasse con una sorta di database, allora potrebbe anche cadere impercettibilmente e tornare in vita. Potrei anche provare la persistenza di akka per ripristinare lo stato degli attori, ma l'attuale implementazione mi si addice con la sua semplicità. Non è che il mio codice si bloccasse spesso, al contrario, ho fatto molti sforzi per renderlo impossibile. Ma le cose succedono e la possibilità di suddividere il programma in pezzi-attori isolati mi è sembrata davvero comoda e pratica.

Ho aggiunto circle-ci in modo che se il codice si rompe, lo scoprirai immediatamente. Come minimo, significa che la compilazione del codice ha interrotto. Inizialmente volevo aggiungere travis, ma mostrava solo i miei progetti senza fork. In generale, entrambe queste cose possono essere utilizzate liberamente nei repository aperti.

Risultati di

È già novembre. Il bot è scritto, lo uso da due settimane e mi è piaciuto. Se hai idee per migliorare, scrivi. Non vedo il motivo di monetizzarlo: lascia che funzioni e invii articoli interessanti.

сылка на ота: https://t.me/HabraFilterBot
Github: https://github.com/Kright/habrahabr_reader

Piccole conclusioni:

  • Anche un piccolo progetto può richiedere molto tempo.
  • Non sei Google. Non ha senso sparare ai passeri da un cannone. Una soluzione semplice può funzionare altrettanto bene.
  • I progetti per animali domestici sono ottimi per sperimentare nuove tecnologie.
  • I bot di Telegram sono scritti in modo abbastanza semplice. Se non fosse stato per il “lavoro di squadra” e gli esperimenti con la tecnologia, il bot sarebbe stato scritto in una settimana o due.
  • Il modello dell'attore è una cosa interessante che si sposa bene con il codice multi-thread e con tolleranza agli errori.
  • Penso di aver avuto un assaggio del motivo per cui la comunità open source ama i fork.
  • I database sono utili perché lo stato dell'applicazione non dipende più da arresti anomali/riavvii dell'applicazione, ma lavorare con un database complica il codice e impone restrizioni sulla struttura dei dati.

Fonte: habr.com

Aggiungi un commento