JavaRush /Java Blog /Random-JA /最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的な成長におけるその重要性
Pavel Mironov (Miroha)
レベル 16
Москва

最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的な成長におけるその重要性

Random-JA グループに公開済み
皆さんこんにちは!あなた自身について教えてください。私は24歳で、昨年工業大学を卒業しましたが、まだ職歴はありません。今後に向けて、当初は(2019年秋に策定した)計画では2020年3月から4月に出勤する予定だったが、残念ながら隔離措置が介入したため、すべてを中旬まで延期したと言いたい。 -夏、そして将来的には私自身のサクセスストーリーを書きたいと思っています。 最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその意義 - 1私はプログラミングにまったく惹かれませんでした。大学では十分なプログラミングを教えてくれましたが、当時はこの工作に興味を持ちませんでした。手続き型言語 (C)、OOP (Java)、データベース、さらにはアセンブラーや C++ の 1 年間のコースもありました。しかし、正直に言うと、教えられる分野のほとんどは私にとって役に立たず、レポート目的にしか適していないように思えたので、私は勉強にはほとんど無関心でした(原理的にはそうです)。大学を卒業した後、私は決断を迫られました。何もスキルを身につけていないが、働く必要があるのです。私は独学について考えなければなりませんでした (ああ、ぼーっと座ってすでに丸 2 年以上を浪費してしまいました)。大学の OOP コースで、ある人が javarush コースを勧めたので、選択は当然 Java になりました。ご存知のとおり、彼は特に Java 言語に専念しています。コースの紹介に興味がありました。はい、当時私はプログラミングが好きではありませんでした。なぜなら、何か困難に遭遇するとすぐに諦めてしまうからです。プログラミングには困難が十分にあります。でも同時にコードを書きたいという気持ちもあったので、最終的にはプログラミングを始めることにしました。javarush に関する私の経験について簡単に説明します。私は2019年8月に始めて、すぐに1か月分のサブスクリプションを購入しましたが、レベル7でタスクが難しいことに気づきました。私はコースを脇に置き、シルトを迎えに行きました。それで並行して3か月間コースを完了しました。私はレベル 20 に到達し (これが 2 番目のアカウントです)、Shildt をほぼ完全に読みましたが、ここでのタスクに飽きてしまい、自分にとって実際的なメリットが見られなくなりました。私は codewars や leetcode に通い、ビデオコースを見始めました。ちなみに、私は 3 か月で「配列って何? どうやって操作するの? なぜそんなに怖いの?」 から始まりました。コレクション クラス (ArrayList、HashMap など) のソース コードの詳細な調査まで。個人的な経験に基づいて初心者に伝えます。ここで重要なことは、何も理解できず、何も解決できないときに生じる感情を克服することです。それが起こったとき、あなたはただすべてを放棄したいだけであり、この問題についてはあまりにも愚かであるように思われます。自分の中でそのような瞬間を乗り越えて精神的に休めば、成功が訪れるでしょう。これに耐えられず、すぐに諦めてしまう人も多いと思います。その結果、2019 年 12 月に自分のプロジェクトについて考え始めました。Telegram ボットを選択することにしましたが、アイデアはありませんでした。同時に、ある友人は電報にグループ向けの機能が必要で、これを自動化したいと考えていました。彼は私がプログラミングを徹底的に勉強していることを知っていて、プロジェクトを提供してくれました。私にとっては、経験と将来の履歴書のために、彼にとっては、グループの発展のために。彼のアイデアを引用することさえ許可します。」Недавно софтину хотел у программиста заказать, которая загружала бы в выбранное Облако файлы по прямым linkм. Это интересно, так How аналогов нет. И просто очень удобно. Суть: копируешь ссылку, вставляешь в окно и выбираешь нужное Облако (GDrive, Mail, Яндекс Диск и т.п), в своё время софт всё делает на стороне serverа и юзеру ничего не нужно загружать на свою машину (особенно круто, когда у тебя сборка на SSD-накопителях). Думали сделать в web-интерфейсе, чтобы можно было запускать How с телефонов, так и с десктопа... Можно в принципе через приложение реализовать, а не через web-интерфейс. Тебе такое по силам?「仕事を始めましたが、結局、数日後、知識不足が主な原因で何もうまくいかないことに気づきました。友人は Cloud.Mail への同じリンクを必要としていましたが、まだリンクがありません。 API を持っていない。GDrive 経由で何かをまとめようとしたが、実装が不十分で、さらにこのクラウド サービスは「顧客」に合っていなかった。最初はいくつかのクラウドから選択できるように提案したが、最終的にメール以外はすべて拒否した.ru, 解決策が見つかりませんでした。どういうわけか、すべてが高価であることが判明しました。データベースに接続したり、ストレージ用のサーバーを使用したりする必要がありました。ちなみに、この Web アプリケーションはまだ必要です。それはうまくいかないので、情報ボットを作成することにしました。これは、Google Play ストアからゲームへのリンクを受け取り、リンクを解析して、受け取った情報をライブラリに保存し、json ファイルに書き込むというものでした。このように、リクエストのたびにユーザーの努力によってライブラリは拡張されていくことになりますが、将来的には Google Play にアクセスするだけではゲームに関する情報を便利な形で入手できなくなります。コマンド /libraryHere_game_name を記述するだけで、必要なものがすべて入手できます。ただし、いくつかの困難がありますので、それについては後ほど説明します。最初は 2 つの SQL コースを同時に受講し始めたので、ゆっくりと進歩しました。ボットがどのように機能するのか、リクエストをどのように処理するのかまったく理解できませんでした。私もこのプロジェクトに取り組むことに興味を持っている友人に会いました。ボットの最初のバージョンは約 1 か月で完成しましたが、友人 (私の側) と意見の相違が生じました。私はボットの解析を担当する部分を担当し、彼はボットへのリクエストとその処理に直接取り組みました。何らかの理由で、彼はボットを複雑化し、ある種の承認を導入し、管理者を発明し、不要な機能を追加し始めました。さらに、私は彼のコーディング スタイルがあまり好きではありませんでした。私の意見では、これは情報ボットには必要ありませんでした。そこで、必要な機能を備えたボットを自分で最初から作成することにしました。ここで、ボットが実際に何をするのかを説明します (プロジェクト コードの例を使用)。記事の最後にプロジェクトの完全なコードを添付しますが、残念ながら、それについて完全にコメントすることはできません。ボットに送信されるユーザー メッセージはすべて、Update クラスのオブジェクトです。これには、多くの情報 (メッセージ ID、チャット ID、一意のユーザー ID など) が含まれています。更新にはいくつかの種類があります。テキスト メッセージ、電報キーボードからの応答 (コールバック)、写真、音声などです。ユーザーがあまりいじらないようにするために、テキスト要求とキーボードからのコールバックのみを処理します。ユーザーが写真を送信すると、ボットはその写真に対して何もするつもりがないことをユーザーに通知します。メインのボット クラスの onUpdateReceived メソッドで、ボットは更新を受信します。
@Override
    public void onUpdateReceived(Update update) {
        UpdatesReceiver.handleUpdates(update);
    }
これをハンドラー (独自の UpdatesReceiver クラス) に渡します。
public static void handleUpdates(Update update) {
        ...
        if (update.hasMessage() && update.getMessage().hasText()){
            log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
            new TextMessageHandler(update, replyGenerator).handleTextMessage();
        }
        else if (update.hasCallbackQuery()) {
            //логгирование
            new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
        }
        else {
           //логгирование
            replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые messages!");
        }
    }
UpdatesReceiver は、更新の種類に応じて、別の特殊なハンドラー (TextMessageHandler または CallbackQueryHandler) に制御を移す中央ハンドラーであり、そのコンストラクターに更新をチェーンのさらに下流に渡します。ボットを使用する場合、更新は最も重要であり、更新を忘れることはできません。更新に保存されている情報を利用して、どのユーザーとどのチャットに応答を送信すべきかを特定するからです。ユーザーへの応答を生成するために、別のクラスを作成しました。通常のテキストメッセージ、インラインキーボード付きメッセージ、画像付きメッセージ、返信キーボード付きメッセージを送信できます。インライン キーボードは次のようになります。 最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその意義 - 1ボタンが定義されており、ボタンをクリックすると、ユーザーはサーバーにコールバックを送信します。このコールバックは、通常のメッセージとほぼ同じ方法で処理できます。それを「維持」するには、独自のハンドラーが必要です。各ボタンのアクションを設定し、Update オブジェクトに書き込みます。それらの。「Cost」ボタンにはコールバックの説明「/price」を設定します。これは後でアップデートから取得できます。次に、別のクラスで、このコールバックをすでに処理できます。
public void handleCallBackQuery() {
  String call_data = update.getCallbackQuery().getData();
  long message_id = update.getCallbackQuery().getMessage().getMessageId();
  long chat_id = update.getCallbackQuery().getMessage().getChatId();
    switch (call_date){
      case "/price" :
        //тут что-то сделать
        break;
...
Reply キーボードは次のようになります。 最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその意義 - 2そして本質的には、ユーザーの入力を置き換えます。「ライブラリ」ボタンをクリックすると、すぐに「ライブラリ」メッセージがボットに送信されます。キーボードの種類ごとに、Builder パターンであるinlineReplyを実装する独自のクラスを作成しました。その結果、要件に応じて必要なキーボードを基本的に「描く」ことができます。キーボードは異なる場合がありますが、原理は同じなので、これは非常に便利です。インライン キーボードを使用してメッセージを送信する直感的な方法は次のとおりです。
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
        SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
                .setText("Вы может узнать следующую информацию об игре " + gameTitle)
                .row()
                .button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
                .button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
                .button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
                .endRow()
                .row()
                .button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
                .button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
                .button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
                .endRow()
                .row()
                .button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
                .endRow()
                .row()
                .button("Скрыть клавиатуру", "close")
                .endRow()
                .build();
        try {
            execute(keyboard);
        } catch (TelegramApiException e) {
            log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
        }
    }
ボットに厳密な機能を与えるために、スラッシュ文字を使用した特別なコマンド (/library、/help、/game など) が考案されました。そうしないと、ユーザーが書き込む可能性のあるガーベジを処理する必要があります。実際、これは MessageHandler が書かれた目的です。
if (message.equals(ChatCommands.START.getDescription())) {
     replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
     replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
             || message.equalsIgnoreCase("Помощь")) {
      replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
 ...
したがって、ボットに送信するコマンドに応じて、特別なハンドラーが作業に組み込まれます。さらに進んで、パーサーとライブラリの動作を見てみましょう。Google Play ストアのゲームへのリンクをボットに送信すると、特別なハンドラーが自動的に動作します。これに応じて、ユーザーは次の形式でゲームに関する情報を受け取ります。 最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその意義 - 3同時に、ゲームをボットのライブラリに追加しようとするメソッドが呼び出されます (最初にローカル マップに、次に -> json ファイルに) )。ゲームがすでにライブラリにある場合は (通常のハッシュマップと同様に) チェックが実行され、フィールド データ (バージョン番号が変更されているなど) がある場合は、ライブラリ内のゲームが上書きされます。変更が検出されない場合、エントリは作成されません。ライブラリにゲームがまったくなかった場合は、最初にローカル マップ ( tykなどのオブジェクト) に書き込まれ、次に json ファイルに書き込まれます。これは、サーバー上のアプリケーションが予期せず閉じられた場合にデータが失われるためです。失われますが、ファイルを使用していつでも読み取ることができます。実際には、プログラムが開始されると、ライブラリは常に静的ブロックのファイルから最初にロードされます。
static {
        TypeFactory typeFactory = mapper.getTypeFactory();
        MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);

        try {
            Path path = Paths.get(LIBRARY_PATH);
            if (!Files.exists(path)) {
                Files.createDirectories(path.getParent());
                Files.createFile(path);
                log.info("[Файл библиотеки создан]");
            }
            else {
                ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
                games.putAll(temporary);
                log.info("[Количество игр в загруженной библиотеке] = " + games.size());
            }
        }
        catch (IOException e) {
            log.error("[Ошибка при чтении/записи file] {}", e.getMessage());
        }
    }
ここではさらに、ファイルから一時マップにデータを読み取る必要があります。ファイル内のゲームを検索するときに大文字と小文字を区別しないようにするために、そのデータが完全なマップに「コピー」されます (tITan QuEST を記述することで、ボットは引き続き検索します)ライブラリにあるゲーム Titan Quest)。別の解決策を見つけることはできませんでした。これらは、Jackson を使用した逆シリアル化の機能です。したがって、リンクを要求するたびに、可能であればゲームがライブラリに追加され、それによってライブラリが拡張されます。特定のゲームに関する詳細情報は、コマンド/libraryGame_Name を使用して取得できます。特定のパラメーター (現在のバージョンなど) とすべてのパラメーターの両方を一度に確認できます。これは、前に説明したインライン キーボードを使用して実装されます。仕事中も、ここで得たスキルを応用しながら問題を解決しました。たとえば、ライブラリにあるランダムなゲームの名前のリスト (このオプションは /library コマンドを使用して利用できます):
private String getRandomTitles(){
        if (LibraryService.getLibrary().size() < 10){
            return String.join("\n", LibraryService.getLibrary().keySet());
        }
        List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
        Collections.shuffle(keys);
        List<string> randomKeys = keys.subList(0, 10);
        return String.join("\n", randomKeys);
    }
ボットはリンクをどのように処理しますか? それらが Google Play に属しているかどうか (ホスト、プロトコル、ポート) を確認します。
private static class GooglePlayCorrectURL {

        private static final String VALID_HOST = "play.google.com";

        private static final String VALID_PROTOCOL = "https";

        private static final int VALID_PORT = -1;

        private static boolean isLinkValid(URI link) {
            return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
        }

        private static boolean isProtocolExist(URI link) {
            if (link.getScheme() != null) {
                return link.getScheme().equals(VALID_PROTOCOL);
            }
            else {
                return false;
            }
        }

        private static boolean isHostExist(URI link) {
            if (link.getHost() != null) {
                return link.getHost().equals(VALID_HOST);
            }
            else {
                return false;
            }
        }
すべてが正常であれば、ボットは Jsoup ライブラリを使用したリンク経由で接続します。これにより、ページの HTML コードを取得できるようになり、さらに分析と解析が行われます。間違ったリンクや有害なリンクを使用してボットをだますことはできません。
if (GooglePlayCorrectURL.isLinkValid(link)){
     if (!link.getPath().contains("apps")){
         throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
     }
     URL = forceToRusLocalization(URL);
     document = Jsoup.connect(URL).get();
 }
     else {
         throw new NotGooglePlayLinkException();
      }
...
ここでは、地域設定に関する問題を解決する必要がありました。ボットはヨーロッパにあるサーバーから Google Play ストアに接続するため、Google Play ストアのページは適切な言語で開きます。私は、ページのロシア語版に強制的に「リダイレクト」する松葉杖を書かなければなりませんでした(結局のところ、このプロジェクトは私たちの視聴者を対象としたものでした)。これを行うには、リンクの最後に、 Google Play サーバーへの GET リクエストにパラメータ hl: &hl=ruを慎重に追加する必要があります。
private String forceToRusLocalization(String URL) {
        if (URL.endsWith("&hl=ru")){
            return URL;
        }
        else {
            if (URL.contains("&hl=")){
                URL = URL.replace(
                        URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
            }
            else {
                URL += "&hl=ru";
            }
        }
        return URL;
    }
接続が成功すると、すぐに分析および解析できる HTML ドキュメントを受け取りますが、これについてはこの記事の範囲を超えています。パーサーコードはここにあります。パーサー自体が必要な情報を取得し、ゲームのオブジェクトを作成します。このオブジェクトは、必要に応じて後でライブラリに追加されます。<h2>要約</h2>ボットは、特定の機能を含むいくつかのコマンドをサポートしています。ユーザーからメッセージを受信し、それをコマンドと照合します。リンクまたは /game + link コマンドの場合は、そのリンクが Google Play に属しているかどうかを確認します。リンクが正しければ、Jsoup 経由で接続し、HTML ドキュメントを受信します。この文書は、書かれたパーサーに基づいて分析されます。ゲームに関する必要な情報がドキュメントから抽出され、ゲームが含まれるオブジェクトにこのデータが埋め込まれます。次に、ゲームを含むオブジェクトがローカル ストレージに配置され (ゲームがまだ存在しない場合)、データの損失を避けるためにすぐにファイルに書き込まれます。ライブラリに記録されているゲーム (ゲームの名前はマップのキー、ゲームを含むオブジェクトはマップの値) は、コマンド /library Game_name を使用して取得できます。指定されたゲームがボットのライブラリで見つかった場合、ユーザーにはインライン キーボードが返され、これを使用してゲームに関する情報を取得できます。ゲームが見つからない場合は、名前のスペルが正しいことを確認するか (大文字と小文字を除いて、Google Play ストアのゲーム名と完全に一致する必要があります)、ボットを送信してゲームをライブラリに追加する必要があります。ゲームへのリンク。私は Heroku にボットをデプロイしました。将来、独自のボットを作成して Heroku で無料でホストすることを計画している人のために、遭遇する可能性のある問題を解決するための推奨事項をいくつか示します (私自身がそのような問題に遭遇したため)。残念ながら、Heraku の性質上、ボット ライブラリは 24 時間ごとに常に「リセット」されます。私のプランでは Heroku サーバーへのファイルの保存はサポートされていないため、単純に Github からゲーム ファイルをプルします。解決策はいくつかありました。データベースを使用するか、このファイルをゲームとともに保存する別のサーバーを探すかです。基本的にボットはそれほど役に立たないため、今のところは何もしないことにしました。むしろ、完全な経験を積むためにそれが必要でしたし、基本的にはそれが私が達成したことです。Heroku の推奨事項:
  1. ロシアにお住まいの場合は、VPN を使用して Heroku に登録する必要がある可能性があります。

  2. プロジェクトのルートに、Procfile という拡張子のないファイルを置く必要があります。その内容は次のようになります: https://github.com/miroha/Telegram-Bot/blob/master/Procfile

  3. pom.xml で、例に従って次の行を追加します。 mainClass タグでは、メイン メソッド bot.BotApplication (BotApplication クラスが bot フォルダーにある場合) を含むクラスへのパスを示します。

  4. mvn package コマンドなどを使用してプロジェクトをビルドしないでください。Heraku がすべてをアセンブルします。

  5. たとえば次のように、gitignore をプロジェクトに追加することをお勧めします。

    # Log file
    *.log
    
    # Compiled resources
    target
    
    # Tests
    test
    
    # IDEA files
    .idea
    *.iml
  6. 実際にプロジェクトを github にアップロードし、リポジトリを Heroku に接続します (または、他の方法を使用します。私の記憶が間違っていなければ、3 つあります)。

  7. ダウンロードが成功した場合 (「ビルドに成功しました」)、必ず Dynos の構成に進んでください。

    最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその意義 - 4

    スライダーを切り替えて、オンの位置にあることを確認します(これを行わなかったためにボットが機能せず、数日間頭を悩ませ、多くの不要な動きをしました) )。

  8. Github 上のボット トークンを非表示にします。これを行うには、環境変数からトークンを取得する必要があります。

    public class Bot extends TelegramLongPollingBot {
    
        private static final String BOT_TOKEN = System.getenv("TOKEN");
    
        @Override
        public String getBotToken() {
            return BOT_TOKEN;
        }
    ...
    }

    そして、ボットをデプロイした後、設定タブの Heroku ダッシュボードでこの変数を設定します (TOKEN の右側に VALUE フィールドがあり、そこにボットのトークンをコピーします)。

    最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその重要性 - 5
私自身のプロジェクトに取り組む 2 か月間で、合計では次のことができました。
  • Java で書かれた完全に動作するプロジェクトを受け取りました。
  • サードパーティ API (Telegram Bot API) の使い方を学びました。
  • 実際には、シリアル化をさらに深く掘り下げ、JSON と Jackson ライブラリをよく使いました (最初は GSON を使用していましたが、それには問題がありました)。
  • ファイルを扱う際のスキルを強化し、Java NIO について知りました。
  • 構成 .xml ファイルの操作方法を学び、ログ記録に慣れました。
  • 開発環境 (IDEA) の習熟度が向上しました。
  • git の使い方を学び、gitignore の価値を学びました。
  • Web ページ解析 (Jsoup ライブラリ) のスキルを習得しました。
  • いくつかのデザインパターンを学び、使用しました。
  • コード (リファクタリング) を改善する感覚と欲求を養いました。
  • オンラインで解決策を見つけること、答えが見つからない質問を恥ずかしがらないことを学びました。
最初のプロジェクトとしての Telegram ボットと個人的な経験に基づく専門的成長におけるその重要性 - 7ボットがどれほど役に立つか役に立たないか、コードがどれほど美しいか醜いかはわかりませんが、得た経験は間違いなく価値がありました。自分のプロジェクトに対する責任感を感じました。時々、それを改善したり、新しいものを追加したりしたいと思います。実行して、すべてが思いどおりに機能することが確認できたときは、本当に感動しました。それがメインではないでしょうか?自分のやっていることを楽しんで、最後のチョコレートバーのようにコードのすべての作業行を楽しんでください。したがって、プログラミングをマスターしているのであれば、私からのアドバイスは、レベル 40 までここに留まらず、できるだけ早く独自のプロジェクトを開始することです。興味のある人は、プロジェクトのソース コードはここにあります (Spring 用に書き直されました): https://github.com/miroha/GooglePlayGames-TelegramBot 過去 2 か月間、私は新しい内容をほとんど勉強していませんでした。行き止まりに達したということ。仕事がなければ、おそらく Spring Framework を教えること以外に、どこを開発すべきかわかりません。来月はそれを行うつもりです。次に、このフレームワークを使用してボットを「書き換え」てみます。あらゆる質問にお答えします。:) 皆さん頑張ってください! 2020 年 7 月 7 日からの更新 Pure Java のボットを含むリポジトリが失われました (削除しましたが、コピーが別のローカル マシンに残っていました) が、Spring Boot 用に書き換えられたボットをダウンロードしました: https://github.com/miroha /GooglePlayGames-TelegramBot
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION