Bot Telegram pour une sélection personnalisée d'articles de Habr

Pour des questions comme « pourquoi ? » il y a un article plus ancien - Natural Geektimes - rendre l'espace plus propre.

Il y a beaucoup d'articles, pour des raisons subjectives, certains ne me plaisent pas, et d'autres, au contraire, c'est dommage de les sauter. Je souhaite optimiser ce processus et gagner du temps.

L'article ci-dessus suggérait une approche de script intégré au navigateur, mais je ne l'ai pas vraiment aimé (même si je l'ai déjà utilisé) pour les raisons suivantes :

  • Pour différents navigateurs sur votre ordinateur/téléphone, vous devez le reconfigurer, si possible.
  • Un filtrage strict par auteurs n’est pas toujours pratique.
  • Le problème des auteurs dont on ne veut pas manquer les articles, même s’ils paraissent une fois par an, n’est pas résolu.

Le filtrage intégré au site basé sur les notes des articles n'est pas toujours pratique, car les articles hautement spécialisés, malgré leur valeur, peuvent recevoir une note plutôt modeste.

Au départ, je souhaitais générer un flux RSS (voire plusieurs), en n'y laissant que des choses intéressantes. Mais au final, il s'est avéré que la lecture des RSS ne semblait pas très pratique : de toute façon, pour commenter/voter un article/l'ajouter à ses favoris, il faut passer par le navigateur. C'est pourquoi j'ai écrit un robot télégramme qui m'envoie des articles intéressants dans un message personnel. Telegram lui-même en fait de magnifiques aperçus qui, combinés avec des informations sur l'auteur/la note/les opinions, semblent très instructifs.

Bot Telegram pour une sélection personnalisée d'articles de Habr

Sous la coupe se trouvent des détails tels que les caractéristiques de l'œuvre, le processus d'écriture et les solutions techniques.

En bref sur le bot

Dépôt: https://github.com/Kright/habrahabr_reader

Bot dans le télégramme : https://t.me/HabraFilterBot

L'utilisateur définit une note supplémentaire pour les balises et les auteurs. Après cela, un filtre est appliqué aux articles - la note de l'article sur Habré, la note de l'auteur et la moyenne des notes des utilisateurs par balise sont additionnées. Si le montant est supérieur à un seuil spécifié par l'utilisateur, l'article passe le filtre.

Un objectif secondaire de l’écriture d’un bot était d’acquérir du plaisir et de l’expérience. De plus, je me rappelais régulièrement que je ne suis pas Google, et donc beaucoup de choses sont faites aussi simplement et même primitivement que possible. Cependant, cela n’a pas empêché le processus d’écriture du bot de prendre trois mois.

C'était l'été dehors

Juillet touchait à sa fin et j'ai décidé d'écrire un bot. Et pas seul, mais avec un ami qui maîtrisait Scala et qui voulait écrire quelque chose dessus. Le début semblait prometteur : le code serait découpé par une équipe, la tâche semblait facile et je pensais que dans quelques semaines ou un mois, le bot serait prêt.

Malgré le fait que j'écris moi-même du code sur le rocher de temps en temps ces dernières années, personne ne voit ou ne regarde habituellement ce code : projets favoris, test de certaines idées, prétraitement des données, maîtrise de certains concepts de FP. J'étais vraiment intéressé par ce à quoi ressemble l'écriture de code en équipe, car le code sur rock peut être écrit de manières très différentes.

Qu'est-ce qui aurait pu se passer si? Cependant, ne précipitons pas les choses.
Tout ce qui se passe peut être suivi à l'aide de l'historique des validations.

Une connaissance a créé un référentiel le 27 juillet, mais n'a rien fait d'autre, alors j'ai commencé à écrire du code.

Juillet 30

En bref : j'ai écrit une analyse du flux rss de Habr.

  • com.github.pureconfig pour lire les configurations typesafe directement dans les classes de cas (cela s'est avéré très pratique)
  • scala-xml pour lire du XML : comme au départ je voulais écrire ma propre implémentation pour le flux rss et que le flux rss est au format XML, j'ai utilisé cette bibliothèque pour l'analyse. En fait, l'analyse RSS est également apparue.
  • scalatest pour les tests. Même pour les petits projets, l'écriture de tests permet de gagner du temps - par exemple, lors du débogage de l'analyse XML, il est beaucoup plus facile de le télécharger dans un fichier, d'écrire des tests et de corriger les erreurs. Lorsqu'un bug est apparu plus tard lors de l'analyse d'un code HTML étrange avec des caractères utf-8 invalides, il s'est avéré plus pratique de le mettre dans un fichier et d'ajouter un test.
  • acteurs d'Akka. Objectivement, ils n'étaient pas du tout nécessaires, mais le projet a été écrit pour le plaisir, j'avais envie de les essayer. En conséquence, je suis prêt à dire que j’ai aimé. L'idée de la POO peut être vue de l'autre côté : il y a des acteurs qui échangent des messages. Ce qui est plus intéressant, c'est que vous pouvez (et devez) écrire du code de telle manière que le message ne puisse pas arriver ou ne pas être traité (d'une manière générale, lorsque le compte fonctionne sur un seul ordinateur, les messages ne doivent pas être perdus). Au début je me grattais la tête et il y avait des conneries dans le code avec des acteurs s'abonnés les uns aux autres, mais au final j'ai réussi à arriver à une architecture plutôt simple et élégante. Le code à l'intérieur de chaque acteur peut être considéré comme monothread ; lorsqu'un acteur plante, l'acca le redémarre - le résultat est un système assez tolérant aux pannes.

9 Août

J'ai ajouté au projet scala-scrapper pour analyser les pages HTML de Habr (pour extraire des informations telles que l'évaluation des articles, le nombre de signets, etc.).

Et les chats. Ceux dans le rocher.

Bot Telegram pour une sélection personnalisée d'articles de Habr

J'ai ensuite lu un livre sur les bases de données distribuées, j'ai aimé l'idée du CRDT (Conflict-free répliqué data type, https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type, habr), j'ai donc posté une classe de type d'un semi-groupe commutatif pour obtenir des informations sur l'article sur Habré.

En fait, l'idée est très simple : nous avons des compteurs qui changent de façon monotone. Le nombre de promotions augmente progressivement, tout comme le nombre de plus (ainsi que le nombre de moins). Si j'ai deux versions d'informations sur un article, je peux alors les « fusionner en une seule » - l'état du compteur le plus grand est considéré comme plus pertinent.

Un semi-groupe signifie que deux objets contenant des informations sur un article peuvent être fusionnés en un seul. Commutatif signifie que vous pouvez fusionner à la fois A + B et B + A, le résultat ne dépend pas de l'ordre et, à la fin, la version la plus récente restera. D'ailleurs, il y a aussi de l'associativité ici.

Par exemple, comme prévu, le RSS après analyse a fourni des informations légèrement affaiblies sur l'article - sans mesures telles que le nombre de vues. Un acteur spécial a ensuite pris les informations sur les articles et a parcouru les pages html pour les mettre à jour et les fusionner avec l'ancienne version.

D'une manière générale, comme dans akka, cela n'était pas nécessaire, vous pouviez simplement stocker updateDate pour l'article et en prendre un plus récent sans aucune fusion, mais le chemin de l'aventure m'a conduit.

12 Août

J'ai commencé à me sentir plus libre et, juste pour m'amuser, j'ai fait de chaque conversation un acteur à part entière. Théoriquement, un acteur lui-même pèse environ 300 octets et peut être créé par millions, c'est donc une approche tout à fait normale. Il me semble que la solution s'est avérée assez intéressante :

L'un des acteurs était un pont entre le serveur de télégrammes et le système de messagerie d'Akka. Il recevait simplement des messages et les envoyait à l'acteur de chat souhaité. L'acteur du chat pourrait renvoyer quelque chose en réponse - et cela serait renvoyé au télégramme. Ce qui était très pratique, c'est que cet acteur s'est avéré aussi simple que possible et ne contenait que la logique pour répondre aux messages. À propos, des informations sur les nouveaux articles sont arrivées dans chaque discussion, mais encore une fois, je n'y vois aucun problème.

En général, le bot fonctionnait déjà, répondait aux messages, stockait une liste d'articles envoyés à l'utilisateur, et je pensais déjà que le bot était presque prêt. J'ai lentement ajouté de petites fonctionnalités comme la normalisation des noms d'auteurs et des balises (en remplaçant « sd f » par « s_d_f »).

Il ne restait qu'une chose petit mais — l'État n'a été sauvé nulle part.

Tout s'est mal passé

Vous avez peut-être remarqué que j'ai écrit le bot principalement seul. Ainsi, le deuxième participant s'est impliqué dans le développement, et les changements suivants sont apparus dans le code :

  • MongoDB semble stocker l'état. Dans le même temps, les journaux du projet ont été interrompus, car pour une raison quelconque, Monga a commencé à les spammer et certaines personnes les ont simplement désactivés globalement.
  • L'acteur de pont dans Telegram a été transformé au point de devenir méconnaissable et a commencé à analyser lui-même les messages.
  • Les acteurs des discussions ont été impitoyablement supprimés et remplacés par un acteur qui a caché toutes les informations sur toutes les discussions à la fois. Pour chaque éternuement, cet acteur avait des ennuis. Eh bien oui, comme lors de la mise à jour des informations sur un article, l'envoyer à tous les acteurs du chat est difficile (nous sommes comme Google, des millions d'utilisateurs attendent un million d'articles dans le chat pour chacun), mais à chaque fois que le chat est mis à jour, c'est normal d'aller à Monga. Comme je l’ai réalisé bien plus tard, la logique de fonctionnement des chats a également été complètement supprimée et à sa place est apparu quelque chose qui ne fonctionnait pas.
  • Il ne reste aucune trace des classes de types.
  • Une logique malsaine est apparue chez les acteurs avec leurs abonnements les uns aux autres, conduisant à une condition de concurrence.
  • Structures de données avec des champs de type Option[Int] transformé en Int avec des valeurs par défaut magiques comme -1. Plus tard, j'ai réalisé que mongoDB stockait json et qu'il n'y avait rien de mal à l'y stocker Option eh bien, ou du moins, analysez -1 comme None, mais à ce moment-là, je ne le savais pas et j'ai cru sur parole que "c'est comme ça que ça devrait être". Je n’ai pas écrit ce code et je n’ai pas pris la peine de le modifier pour le moment.
  • J'ai découvert que mon adresse IP publique avait tendance à changer, et à chaque fois j'ai dû l'ajouter à la liste blanche de Mongo. J'ai lancé le bot localement, Monga se trouvait quelque part sur les serveurs de Monga en tant qu'entreprise.
  • Du coup, la normalisation des balises et le formatage des messages pour les télégrammes ont disparu. (Hmm, pourquoi serait-ce ?)
  • J’ai aimé que l’état du bot soit stocké dans une base de données externe et qu’une fois redémarré, il continue de fonctionner comme si de rien n’était. Cependant, c'était le seul avantage.

La deuxième personne n'était pas particulièrement pressée, et tous ces changements sont apparus en un seul gros tas dès début septembre. Je n'ai pas immédiatement apprécié l'ampleur des destructions qui en ont résulté et j'ai commencé à comprendre le fonctionnement de la base de données, car... Je n’ai jamais eu affaire à eux auparavant. Ce n'est que plus tard que j'ai réalisé combien de code fonctionnel avait été supprimé et combien de bogues avaient été ajoutés à sa place.

Septembre

Au début, je pensais qu'il serait utile de maîtriser Monga et de bien le faire. Puis j'ai petit à petit commencé à comprendre qu'organiser la communication avec la base de données est aussi un art dans lequel on peut faire beaucoup de courses et simplement faire des erreurs. Par exemple, si l'utilisateur reçoit deux messages comme /subscribe - et en réponse à chacun nous créerons une entrée dans le tableau, car au moment du traitement de ces messages l'utilisateur n'est pas abonné. Je soupçonne que la communication avec Monga dans sa forme actuelle n'est pas écrite de la meilleure façon. Par exemple, les paramètres de l'utilisateur ont été créés au moment de son inscription. S'il a essayé de les modifier avant l'abonnement... le bot n'a rien répondu, car le code de l'acteur est entré dans la base de données des paramètres, ne l'a pas trouvé et s'est écrasé. Lorsqu'on m'a demandé pourquoi ne pas créer les paramètres selon mes besoins, j'ai appris qu'il n'est pas nécessaire de les modifier si l'utilisateur n'est pas abonné... Le système de filtrage des messages a été créé d'une manière ou d'une autre de manière non évidente, et même après un examen attentif du code, j'ai pu Je ne comprends pas si c'était prévu de cette façon au départ ou s'il y a une erreur.

Il n'y avait pas de liste d'articles soumis au chat ; à la place, il a été suggéré que je les rédige moi-même. Cela m'a surpris - en général, je n'étais pas contre le fait d'introduire toutes sortes de choses dans le projet, mais ce serait logique pour celui qui a apporté ces choses et les a foutues. Mais non, le deuxième participant a semblé abandonner tout, mais a déclaré que la liste à l'intérieur du chat était censée être une mauvaise solution, et qu'il fallait faire un signe avec des événements comme « un article y a été envoyé à l'utilisateur x ». Ensuite, si l'utilisateur demandait d'envoyer de nouveaux articles, il fallait envoyer une demande à la base de données, qui sélectionnerait parmi les événements les événements liés à l'utilisateur, obtiendrait également une liste de nouveaux articles, les filtrerait, les enverrait à l'utilisateur. et renvoyez les événements à ce sujet dans la base de données.

Le deuxième participant a été emporté quelque part vers les abstractions, lorsque le bot recevra non seulement des articles de Habr et sera envoyé non seulement par télégramme.

J'ai en quelque sorte mis en œuvre des événements sous la forme d'un panneau séparé pour la seconde quinzaine de septembre. Ce n’est pas optimal, mais au moins le bot a commencé à fonctionner et a recommencé à m’envoyer des articles, et j’ai petit à petit compris ce qui se passait dans le code.

Vous pouvez maintenant revenir au début et vous rappeler que le référentiel n'a pas été créé à l'origine par moi. Qu'est-ce qui aurait pu se passer ainsi ? Ma pull request a été rejetée. Il s'est avéré que j'avais du code redneck, que je ne savais pas comment travailler en équipe et que j'ai dû corriger des bugs dans la courbe de mise en œuvre actuelle, et ne pas l'affiner jusqu'à un état utilisable.

Je me suis énervé et j'ai regardé l'historique des validations et la quantité de code écrit. J'ai regardé des moments qui avaient été bien écrits à l'origine, puis qui ont été brisés...

Merde

je me suis souvenu de l'article Vous n'êtes pas Google.

Je pensais que personne n’avait vraiment besoin d’une idée sans mise en œuvre. Je pensais que je voulais avoir un robot fonctionnel, qui fonctionnerait en une seule copie sur un seul ordinateur comme un simple programme Java. Je sais que mon bot fonctionnera pendant des mois sans redémarrage, puisque j'ai déjà écrit de tels robots dans le passé. S'il tombe soudainement et n'envoie pas un autre article à l'utilisateur, le ciel ne tombera pas sur terre et rien de catastrophique ne se produira.

Pourquoi ai-je besoin de Docker, mongoDB et d’autres logiciels « sérieux » si le code ne fonctionne tout simplement pas ou fonctionne de manière tordue ?

J'ai lancé le projet et j'ai tout fait comme je voulais.

Bot Telegram pour une sélection personnalisée d'articles de Habr

À peu près au même moment, j’ai changé de travail et le temps libre me faisait cruellement défaut. Le matin je me suis réveillé dans le train, le soir je suis rentré tard et je ne voulais plus rien faire. Je n'ai rien fait pendant un moment, puis l'envie de terminer le robot m'a submergé et j'ai commencé à réécrire lentement le code pendant que je conduisais pour me rendre au travail le matin. Je ne dirai pas que c'était productif : s'asseoir dans un train tremblant avec un ordinateur portable sur les genoux et regarder le débordement de pile depuis son téléphone n'est pas très pratique. Cependant, le temps passé à écrire du code est passé complètement inaperçu et le projet a commencé à évoluer lentement vers un état de fonctionnement.

Quelque part dans mon esprit, il y avait un doute qui voulait utiliser mongoDB, mais je pensais qu'en plus des avantages d'un stockage d'état « fiable », il y avait des inconvénients notables :

  • La base de données devient un autre point de défaillance.
  • Le code devient de plus en plus complexe et il me faudra plus de temps pour l'écrire.
  • Le code devient lent et inefficace ; au lieu de modifier un objet en mémoire, les modifications sont envoyées à la base de données et, si nécessaire, retirées.
  • Il existe des restrictions sur le type de stockage des événements dans une table séparée, qui sont associées aux particularités de la base de données.
  • La version d'essai de Monga présente certaines limitations, et si vous les rencontrez, vous devrez lancer et configurer Monga sur quelque chose.

J'ai découpé le monga, maintenant l'état du bot est simplement stocké dans la mémoire du programme et de temps en temps enregistré dans un fichier au format json. Peut-être que dans les commentaires, ils écriront que je me trompe, que c'est ici que la base de données doit être utilisée, etc. Mais c'est mon projet, l'approche avec le dossier est la plus simple possible et cela fonctionne de manière transparente.

J'ai jeté les valeurs magiques comme -1 et j'ai renvoyé les valeurs normales Option, ajout du stockage d'une table de hachage avec les articles renvoyés à l'objet avec les informations de discussion. Ajout de la suppression des informations sur les articles datant de plus de cinq jours, afin de ne pas tout stocker. J'ai amené la journalisation à un état fonctionnel - les journaux sont écrits en quantités raisonnables à la fois dans le fichier et dans la console. Ajout de plusieurs commandes d'administration telles que la sauvegarde de l'état ou l'obtention de statistiques telles que le nombre d'utilisateurs et d'articles.

Correction d'un tas de petites choses : par exemple, pour les articles, le nombre de vues, de likes, de dislikes et de commentaires au moment du passage du filtre de l'utilisateur est désormais indiqué. En général, il est surprenant de constater combien de petites choses ont dû être corrigées. J'ai tenu une liste, j'y ai noté toutes les « irrégularités » et je les ai corrigées dans la mesure du possible.

Par exemple, j'ai ajouté la possibilité de définir tous les paramètres directement dans un seul message :

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

Et une autre équipe /settings les affiche exactement sous cette forme, vous pouvez en extraire le texte et envoyer tous les paramètres à un ami.
Cela semble insignifiant, mais il existe des dizaines de nuances similaires.

Filtrage d'articles implémenté sous la forme d'un modèle linéaire simple - l'utilisateur peut définir une note supplémentaire pour les auteurs et les balises, ainsi qu'une valeur seuil. Si la somme de la note de l'auteur, de la note moyenne des balises et de la note réelle de l'article est supérieure à la valeur seuil, alors l'article est présenté à l'utilisateur. Vous pouvez soit demander des articles au bot avec la commande /new, soit vous abonner au bot et il enverra des articles dans un message personnel à tout moment de la journée.

De manière générale, j'ai eu l'idée pour chaque article d'extraire plus de fonctionnalités (hubs, nombre de commentaires, signets, dynamique des changements de notes, quantité de texte, d'images et de code dans l'article, mots-clés) et de montrer à l'utilisateur un message ok/ pas ok voter sous chaque article et former un modèle pour chaque utilisateur, mais j'étais trop paresseux.

De plus, la logique du travail ne sera pas si évidente. Désormais, je peux définir manuellement une note de +9000 20 pour patientZero et avec une note seuil de +100500, j'aurai la garantie de recevoir tous ses articles (à moins, bien sûr, que je définisse -XNUMX XNUMX pour certaines balises).

L'architecture finale s'est avérée assez simple :

  1. Un acteur qui stocke l'état de toutes les discussions et articles. Il charge son état à partir d'un fichier sur le disque et le sauvegarde de temps en temps, à chaque fois dans un nouveau fichier.
  2. Un acteur qui visite le flux RSS de temps en temps, découvre de nouveaux articles, consulte les liens, analyse et envoie ces articles au premier acteur. De plus, il demande parfois une liste d'articles au premier acteur, sélectionne ceux qui ne datent pas de plus de trois jours, mais qui n'ont pas été mis à jour depuis longtemps, et les met à jour.
  3. Un acteur qui communique avec un télégramme. J'ai quand même apporté l'analyse complète du message ici. À l'amiable, je voudrais le diviser en deux - pour que l'un analyse les messages entrants et que le second traite les problèmes de transport tels que le renvoi des messages non envoyés. Désormais, il n'y a plus de réenvoi et un message qui n'est pas arrivé en raison d'une erreur sera simplement perdu (sauf si cela est noté dans les journaux), mais jusqu'à présent, cela n'a posé aucun problème. Peut-être que des problèmes surgiront si un groupe de personnes s'abonnent au bot et que j'atteins la limite d'envoi de messages).

Ce qui m'a plu c'est que grâce à akka, les chutes des acteurs 2 et 3 n'affectent généralement pas les performances du bot. Peut-être que certains articles ne sont pas mis à jour à temps ou que certains messages n'arrivent pas au télégramme, mais le compte redémarre l'acteur et tout continue de fonctionner. J'enregistre l'information selon laquelle l'article est présenté à l'utilisateur uniquement lorsque l'acteur du télégramme répond qu'il a réussi à transmettre le message. La pire chose qui me menace est d'envoyer le message plusieurs fois (s'il est livré, mais la confirmation est perdue d'une manière ou d'une autre). En principe, si le premier acteur ne stockait pas l'état en lui-même, mais communiquait avec une base de données, alors il pourrait également tomber imperceptiblement et reprendre vie. Je pourrais aussi essayer akka persistance pour restaurer l'état des acteurs, mais l'implémentation actuelle me convient par sa simplicité. Ce n’est pas que mon code plante souvent – ​​au contraire, j’ai déployé beaucoup d’efforts pour rendre cela impossible. Mais des merdes arrivent, et la possibilité de diviser le programme en morceaux d'acteurs isolés m'a semblé vraiment pratique et pratique.

J'ai ajouté circle-ci pour que si le code est cassé, vous le sachiez immédiatement. Au minimum, cela signifie que le code a arrêté de se compiler. Au départ, je voulais ajouter Travis, mais cela ne montrait que mes projets sans fourchettes. En général, ces deux éléments peuvent être utilisés librement dans les référentiels ouverts.

Les résultats de

Nous sommes déjà en novembre. Le bot est écrit, je l'utilise depuis deux semaines et je l'ai aimé. Si vous avez des idées d'amélioration, écrivez. Je ne vois pas l'intérêt de le monétiser - laissez-le fonctionner et envoyez des articles intéressants.

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

Petites conclusions :

  • Même un petit projet peut prendre beaucoup de temps.
  • Vous n'êtes pas Google. Cela n'a aucun sens de tirer sur des moineaux avec un canon. Une solution simple peut tout aussi bien fonctionner.
  • Les projets pour animaux de compagnie sont très utiles pour expérimenter de nouvelles technologies.
  • Les robots Telegram sont écrits assez simplement. Sans le « travail d’équipe » et les expérimentations technologiques, le bot aurait été écrit en une semaine ou deux.
  • Le modèle d'acteur est une chose intéressante qui va bien avec le code multithread et tolérant aux pannes.
  • Je pense avoir compris pourquoi la communauté open source aime les forks.
  • Les bases de données sont bonnes car l'état de l'application ne dépend plus des plantages/redémarrages de l'application, mais travailler avec une base de données complique le code et impose des restrictions sur la structure des données.

Source: habr.com

Ajouter un commentaire