テレグラムのボットのレシピ





こんにちは、Habrahabrの読者の皆さん



このトピックでは、4日間でTelegram甚のボットを開発した経隓を皆さんず共有したいず思いたす。 このボットは、受信したすべおの音声メッセヌゞをテキストに倉換したす。 私はすぐにそれをやろうずしたしたが、高品質で-いく぀かの技術を孊びたした。 間違いや障壁を克服するプロセスを可胜な限り詳现に説明したす。 必芁なスキルがなくおも、補品を立ち䞊げるこずはそれほど難しくないこずを蚌明したす。



この蚘事は、プログラミングの初心者完成品の邪魔になる障害の数を確認するためず、より高床な専門家の䞡方に興味があるかもしれたせん。



前文



それで、1人のプログラマが4日間で䜕ができるでしょうか



モゞュヌルの䜜成、単䞀画面iOSアプリケヌションの䜜成、Telegramでの15耇雑なボットの䜜成、Dota2でのMMRの䞊げ䞋げ、たたはオヌバヌりォッチでのランク付け。 自然の䞭で週末を過ごしたり、ゞムに行ったり、プヌルで泳いだり、顧客の友人や芪relativeを陀いお誰も䜿甚しないクラむアント向けに500〜1000行のコヌドを蚘述したりできたす。 最埌のお金でシリコンバレヌぞの道を歩み、2、3のテレビ番組を芋お、1、2回の䌚議で倚くの人々ず知り合い、アセンブラヌのドキュメントに貌り付けたす。



しかし、これがすべお完党なナンセンスだず蚀ったらどうでしょうか 私があなたに秘密を告げるならばそれはそれほど簡単ではありたせん。 時間は盞察的なものであり、異なる倀ず係数で枬定できたす。 私のお気に入りの基準は、恩恵を受ける時間の関係です。 プログラマヌが4日間で垂堎に新補品を投入できるず蚀ったらどうなるでしょうか 興味がありたすか -読んでください。



やる気



最近、「私たち自身のIvangai」がTelegramのプログラマヌコミュニティに远加され、音声メッセヌゞで巊右に散らかるようになりたした突然、キヌボヌドで指で曞くのは難しいこずを知っおいたす。 圓然、人々は䌚話の本質を理解するために1日2〜3時間メッセヌゞを聞くこずにうんざりしおいたした。そしお、誰かがすべおの軍隊を自動的にテキストに倉換するボットを䜜成するこずを提案したした。 これはたさに目が光る瞬間です。



音声からテキストぞの翻蚳



このようなボットを開発するのにどれくらいの時間がかかるかはわかりたせんでしたが、枩かく居心地の良いチャットルヌムずTelegramのオヌプン゜ヌスのTelegramフリヌランスサヌビスのボットの裏で十分な経隓がありたした。 ためらうこずなく、開発のために座った-Telegam Bot APIモゞュヌルを接続する段階はスムヌズに進んだ。 次は 音声認識モゞュヌル-Googleの「音声認識API」。最初のリンクの䞭で最高のサヌビスのリストを取埗したす。 先頭にあるのはベヌタ版のGoogle Speech APIです。



ボットによっおすべおのボむスメッセヌゞを受信し、䜕らかの圢でGoogleに盎接送信したす。 しかし、残念なこずに、このサヌビスを䜿甚するためのほずんどすべおのnpmモゞュヌルは時代遅れであるか、機胜したせん。 Googleの専門家によっお曞かれたNode.jsの組み蟌みAPIを䜿甚しようずしおいたす-動䜜したす。 認蚌には䜕がありたすか Googleのドキュメントから倚くのオプションを詊しおみたした-ちなみに、これも50〜50が叀くなっおいるか、機胜しおいたせん-ここにキヌがありたす。



ずころで、ここに私が䜿甚したコヌドがありたす



抌しお
const Speech = require('@google-cloud/speech'); const speech = new Speech({ projectId: 'voicy-151205', credentials: require('path/to/certificate/file.json') }); speech.startRecognition(filepath, { 'encoding': 'LINEAR16', 'sampleRate': 16000, 'languageCode': 'en-US', }) .then((results) => { const operation = results[0]; return operation.promise(); }) .then((transcription) => { console.log(transcription[0]); })
      
      





Googleオヌディオの圢匏ず制限



Googleに郚隊を掟遣するのはどうですか ええ、Telegramからオヌディオファむルを取埗しおSpeech APIサヌバヌに送信する必芁がありたす。 元気 Telegramの通垞の.oga圢匏は受け入れられたせん-デコヌドする必芁がありたす。 メディア倉換には䜕がありたすか もちろん、ffmpeg、幌少期から芪しんでいたした父に感謝したす。父は昔、圌を理解しおくれたした。 ノヌドに接続する方法は おっず このために特別に調敎されたnpmモゞュヌルがありたす。 .ogaファむルをどの圢匏に倉換する必芁がありたすか Googleは.flacを受け入れ、倉換、詊行、すべおが受け入れられ、Googleがテキストで応答し、成功したこずが刀明したした。



しかし、そこにありたした Googleは、Google Cloud Storageサヌビスにアップロヌドしない限り、60秒を超えるファむルをテキストに倉換したせん。 これは䜕 すぐにGoogleプログラマヌによっお曞かれたモゞュヌルを詊しおみおください。時代遅れのnpm既補゜リュヌションに目を向けないでください。 さお、ファむルはアップロヌドされ、サヌビスによっお凊理され、テキストが返されたす。 ちなみに、Googleには時間ずいう奇劙な抂念がありたす。䜕らかの理由で30秒のオヌディオファむルは60秒ず定矩されおいたす。 倧䞈倫です-30秒以䞊ファむルを詊行し、次のレヌキを螏む-GoogleはLINEAR16゚ンコヌドでのみ長いファむルを受け入れたす。



LINEAR16ずは䜕ですか ドキュメントでffmpegを探しおいたす-そのようなものはありたせん。 いいですね さらに取り組みたす。 この圢匏たたは獣甚のコヌデックを探しおいたす-これは、「16ビットの笊号付きリトル゚ンディアン」デヌタであるこずがわかりたした。 コンピュヌタヌサむ゚ンスに関する本をいく぀か読んで、「16ビット」ずは䜕か、なぜ「リトル゚ンディアン」なのかを知っおいるのは良いこずです。 ffmpegのこの圢匏が䜕であるかを探しおいたす-aha“ s16le” 私たちは倉換しようずしたす-結局のずころ、Googleは「ミッションが達成されたした」ずいうテキストを受け入れお応答したす。



泚意以䞋は、倉換に圹立぀コヌドですマシンにffmpegをプレむンストヌルする必芁がありたす



抌しお
 const ffmpeg = require('fluent-ffmpeg'); const temp = require('temp'); ffmpeg.ffprobe(filepath, (err, info) => { const fileSize = info.format.duration; const output = temp.path({ suffix: '.flac' }); ffmpeg() .on('end', () => console.log(output)) .input(filepath) .setStartTime(0) .duration(fileSize) .output(output) .audioFrequency(16000) .toFormat('s16le') .run(); });
      
      





収益化



しかし、それは䜕ですか Speech APIを䜿甚するための2ドルずは䜕ですか 圌らはGoogleでオヌクを䜿っお完党にそこに行きたしたか 音声をテキストに翻蚳するのに2時間以䞊䜿甚したのはどうしおですか それはたったく機胜したせん-圌らは私のボットを100-1000チャットに远加したす、そしお私はどうすればいいですか 朝食では、ボットサポヌトの節玄は機胜したせん-その芏暡ではありたせん。 どういうわけか支払いを台無しにする必芁がありたす。 チャットごずに600秒間を空けおから、Google Speech APIの費甚の補償を求めたす。



Telegramでボットぞの支払いを固定する方法は 明確な指瀺ず収益化ツヌルは、ただTelegramに配信されおいたせん。倖郚の問題を䜕らかの圢で解決する必芁がありたす。 どの支払いサヌビスを䜿甚したすか たあ、最近、私は最幎少の億䞇長者になった男に぀いお読んだ-圌は圌自身の支払いサヌビスを䜜成したした。 グヌグル、私たちは芋る-ストラむプ、そしおそれを䜿う。 それは䜕ですか 圌らは䟿利なチェックアりトを持っおいたす-しかし、もちろん私たちは暙準フォヌムを䜿甚したせん、私たちは自分で行いたす。



フロント゚ンド



むンタラクティブペヌゞを䜜成する堎所 私はiOSプログラマヌで、最近サヌバヌ開発に没頭したした。そのため、足を少しすすいでください。そしお、フロント゚ンドが間に合いたした。どんな䞍幞ですか グヌグル、むンタラクティブなサむトを䜜成するためにどのテクノロゞヌが䜿甚されおいるか。 Angular 2、jQuery、Vanila.js-jQueryが最もシンプルに芋えたす。特にそれを䜿いたす。特に私はか぀お楜しい時間を過ごしおいたからです。 Google YouTube jQueryチュヌトリアル-2〜3時間のトレシャック1回、進行方法、チュヌトリアル、Stack Overflowの回答を理解したす。



ロゎ、フォヌム、数行のテキスト、ボタンなど、サむトの構造をすばやく描画したす。 どうやっおやるの Bootstrapに぀いお聞いたこずがありたす。 Google、Bootstrap 3でいく぀かのレッスンを行いたす-ただGoogleで、画像、圢状、ボタンを固定したす-䞊べ替えおも、適応性がありたす。 サむトの背景を癜からよりクリ゚むティブなものわずかに灰色がかったものに倉曎し、サむトの準備が敎いたした。



JQueryの時間 別のプロゞェクトで支払いを行うこずにしたした-コマンドラむンで「゚クスプレス支払い」を起動し、プロゞェクトを取埗したす。 すばらしい、スクリプトをどこに眮くか どうやっお、盎接サむトに 䜕かが間違っおいたす-実際、それらを別のファむルに転送するこずができたす。これが私たちのするこずです。 ゚ラヌ凊理を別のラベルに固定し、Stripeフォヌムをチェックし残念なこずに、Checkoutモゞュヌルで必芁なすべおのナヌザヌむンタヌフェむスを提䟛したす、新しいプロゞェクトを同じデヌタベヌスに固定し、支払いをチェックしたす。 もちろん、おなじみのフロント゚ンドツヌル開発者がいないため、Webペヌゞを手動でリロヌドする回数は無数にありたす。



ここに、小さなコメントで私が手に入れたファむル慎重に、あたりきれいではないコヌドを瀺したす。



抌しお
index.hjs
 <!DOCTYPE html> <html> <head> <title>Voicy payments</title> <!-- Bootstrap, jQuery,    global.js,    style.css  Stripe--> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="/javascripts/global.js"></script> <script src="https://checkout.stripe.com/checkout.js"></script> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <!-- -,   id ,     --> <img src="/images/logo.png" alt="Voicy" class="center-block"> <h1 class="center-block text-center">Chat ID:</h1> <h1 class="center-block text-center">{{ chatId }}</h1> <p class="text-center">{{ seconds }} seconds are left in this chat.</p> <p class="text-center">You can buy more seconds below.</p> <p class="text-center"><b>$0.4 per 200 seconds</b></p> <form> <div class="center"> <!--  —  ,    --> <form class="form-inline" id="buy"> <div class="form-group"> <!--,      ,     chatId   --> <input type="hidden" name="chatId" value="{{ chatId }}"> <!-- ,   ,    --> <input type="number" class="form-control" name="numberOfSeconds" placeholder="Enter number of seconds"> <!--  — info, error  success,       global.js--> <small id="infoLabel" class="form-text text-info"></small> <small id="errorLabel" class="form-text text-danger"></small> <small id="successLabel" class="form-text text-success"></small> </div> <button type="submit" class="btn btn-primary center-block" id="buyButton">Buy</button> </form> </form> </form> </body> </html>
      
      





global.js
 //    $(document).ready(function() { //   —  ,    var chatId; var amount; //    Stripe Checkout var handler = StripeCheckout.configure({ key: '***', image: 'https://pay.voicybot.com/images/stripe.png', locale: 'auto', // alipay: true, // ,  - AliPay // bitcoin: true, //     —     US   closed: function() { //  ,  info,   stripe $("#successLabel").empty(); $("#errorLabel").empty(); }, token: function(token) { //    ,   token        //    (,   —     ) $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); //  ,    $("#infoLabel").append('Processing payment on Voicy servers...'); //   ,    index.js $.ajax({ type: 'POST', url: 'buy', data: { 'token': token.id, 'chatId': chatId, 'amount': amount }, dataType: 'json', encode: true }) .done(function(data) { //  :   —   "";  —   if (data['error']) { $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); $("#errorLabel").append(data['error']); } else { $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); $("#successLabel").append('Thank you for the payment!'); } }); } }); //  Stripe,    ,    window.addEventListener('popstate', function() { $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); handler.close(); }); //   —      "Buy" $('form').submit(function(event) { event.preventDefault(); //      var seconds = $('input[name=numberOfSeconds]').val(); //      chatId = $('input[name=chatId]').val(); //  chat id    //    —     200 ,   if (!seconds || seconds < 200) { $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); $("#errorLabel").append('Please purchase at least 200 seconds'); } else { //    —    ... var purch = seconds * 0.002 * 100; amount = seconds; $("#infoLabel").empty(); $("#successLabel").empty(); $("#errorLabel").empty(); $("#infoLabel").append('Please pay at Stripe Checkout'); // ...    Stripe handler.open({ name: 'Voicy Bot', description: 'Purchasing ' + seconds + ' seconds', currency: 'USD', amount: purch, // alipay: true, // bitcoin: true }); } }); });
      
      





index.js
 // Express router     HTTP   jQuery const express = require('express'); const router = express.Router(); // db —  ,          const db = require('../helpers/db'); // Stripe —       const stripe = require("stripe")("***"); /**   */ router.post('/buy', (req, res, next) => { //  token  stripe,      const token = req.body.token; //  id ,     const chatId = parseInt(req.body.chatId); // ,    const amount = parseInt(req.body.amount); var charge = stripe.charges.create({ amount: amount * 0.002 * 100, //     source: token, currency: "USD", description: "Buying seconds for Voicy" }, (err, charge) => { if (err) { res.send({ error: err.message }); //     jQuery } else { db.findChat(chatId) //     id .then((chat) => { // ,   "" chat.seconds = parseInt(chat.seconds) + amount; //     return chat.save() //   .then((newChat) => { res.send({ success: true }); //  ""   jQuery }); }) .catch((err) => { res.send({ error: err.message }); //     jQuery }) } }); }); /*    */ router.get('/:id', (req, res, next) => { const chatId = parseInt(req.params.id); // id ,     db.findChat(chatId) //    id .then((chat) => { //  ?   404! if (!chat) { const err = new Error(); err.status = 404; err.message = 'No chat found'; throw err; } // ?    ! return chat; }) .then((chat) => { //   ,       id  res.render('index', { chatId: chat.id, seconds: chat.seconds, }); }) .catch(err => next(err)); //      (     ) });
      
      





メむンりェブサむト



優れたプロゞェクトには堅実なりェブサむトが必芁ですが、ホスティングにお金をかけたくありたせん。 ドメむン名を賌入し、GitHub Pagesに盎接送信したす。幞いなこずに、この経隓は以前のオヌプン゜ヌスプロゞェクトで十分です。 暙準テンプレヌトを䜿甚し、必芁なデヌタを入力しお、index.htmlをわずかに倉曎したす-それだけです。



NginxずSSL



しかし、支払い方法がhttpsで利甚できない堎合、ナヌザヌはどのように私を信頌したすか 倧䞈倫-泳ぎたした、わかっおいたす サヌバヌでCertBotを起動し、必芁なSSL蚌明曞を取埗したす。 これたでのずころ、アプリケヌションは有料で利甚可胜です*ドメむン* .com3000-ここでナヌザヌを誘導する䟡倀はありたせん。 すべおのリク゚ストをhttpからhttpsにリダむレクトし、プロキシをポヌト80から3000に切断するようにNginxを構成したす-チェックしお、サむトに移動し、動䜜したす。



興味のある方は、nginx蚭定ファむルをご芧ください。



抌しお
 # HTTP -     HTTPS: server { listen 80; listen [::]:80 default_server ipv6only=on; return 301 https://$host$request_uri; } # HTTPS -      Node.js : server { listen 443; server_name your_domain_name; ssl on; #    Lets Encrypt: ssl_certificate /etc/letsencrypt/live/pay.voicybot.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pay.voicybot.com/privkey.pem; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers '***'; #     localhost:3001: location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-NginX-Proxy true; proxy_pass http://localhost:3000/; proxy_ssl_session_reuse off; proxy_set_header Host $http_host; proxy_cache_bypass $http_upgrade; proxy_redirect off; } }
      
      





おわりに



だから私の話は突然䞭断したした-すべおが準備ができたこずが刀明したした1台のサヌバヌがすべおのボむスメッセヌゞを受信しお​​テキストに倉換し、2台目が軍隊の凊理時間の安党な支払いを担圓し、GitHubはプロゞェクトのメむンサむトを配垃したす この蚘事で説明したい䞻なこずは、補品を䜜成するプロセスは、手間はかかりたすが、むンタヌネット䞊の既存のツヌルのおかげで40〜80時間に収たるこずです。



この小さなプロゞェクトには以䞋が含たれるこずを思い出させおください。





ただ自分の補品を䜜成するのに十分なスキルがないず思っおいるなら、芋お回っお、すべおがすでにあなたのために行われおいたす。 同じ2000幎ずは察照的に、珟圚、補品の䜜成にはアルゎリズムずデヌタ構造の深い知識は必芁ありたせん。 それで、あなたは䜕を埅っおいたすか



謝蟞



最埌たで読んでくれおありがずう この蚘事に関するすべおのコメントに喜んでお答えしたす。 これはHabrahabrのルヌルに反するため、ボット自䜓ぞのリンクは添付したせん完党に機胜しおいたすが。



ロックオン。




All Articles