プル型ではなくプッシュ型でAWSの最新情報をキャッチアップしたいと思い、身近なツールであるLINEで通知するアプリケーションをつくってみた。
仕組みとしては、RSSで配信されているAWSの最新情報を、EventBridge + Lambdaで定期的に確認し新しいものがあればLINE通知する、といったもの。
キーワードは以下の通り。
準備運動
RSS
Pythonにはfeedparserというライブラリが存在するため、これを使う。
feedparser.parse(任意のURL)
とすれば、FeedParserDictオブジェクトを取得できる。
試しに「RSSフィードで購読する」で確認できたURLで、FeedParserDictオブジェクトを取得してみた。(可視性あげるためにJSON形式で整形した&量多いので抜粋した)
{ "bozo": false, "entries": [ { "links": [ { "rel": "alternate", "type": "text/html", "href": "https://aws.amazon.com/jp/about-aws/whats-new/2024/03/fujitsu-and-aws-deepen-global-partnership-to-accelerate-legacy-applications-modernization-on-the-cloud/" } ], "link": "https://aws.amazon.com/jp/about-aws/whats-new/2024/03/fujitsu-and-aws-deepen-global-partnership-to-accelerate-legacy-applications-modernization-on-the-cloud/", "id": "84d117fbd5b1f61fa1ebf495d2643756e4ee4afd", "guidislink": false, "title": "富士通と AWS、クラウドでのレガシーシステムのモダナイゼーション加速に向けてグローバルパートナーシップを拡大", "title_detail": { "type": "text/plain", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "富士通と AWS、クラウドでのレガシーシステムのモダナイゼーション加速に向けてグローバルパートナーシップを拡大" }, "summary": "<p>「Modernization Acceleration Joint Initiative」を通して、お客様の DX を支援</p>", "summary_detail": { "type": "text/html", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "<p>「Modernization Acceleration Joint Initiative」を通して、お客様の DX を支援</p>" }, "published": "Mon, 18 Mar 2024 01:58:27 +0000", "published_parsed": [ 2024, 3, 18, 1, 58, 27, 0, 78, 0 ], "tags": [], "authors": [ { "email": "aws@amazon.com" } ], "author": "aws@amazon.com", "author_detail": { "email": "aws@amazon.com" } }, { "links": [ { "rel": "alternate", "type": "text/html", "href": "https://aws.amazon.com/jp/about-aws/whats-new/2024/03/amazon-cognito-europe-zurich-region/" } ], "link": "https://aws.amazon.com/jp/about-aws/whats-new/2024/03/amazon-cognito-europe-zurich-region/", "id": "f8f89768f2f327c71d8f139d045d572abb31bd57", "guidislink": false, "title": "Amazon Cognito が欧州 (チューリッヒ) リージョンで利用可能に", "title_detail": { "type": "text/plain", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "Amazon Cognito が欧州 (チューリッヒ) リージョンで利用可能に" }, "summary": "<p>Amazon Cognito が欧州 (チューリッヒ) リージョンで利用可能になりました。Amazon Cognito では、ウェブアプリケーションやモバイルアプリケーションに認証、認可、ユーザー管理の機能を簡単に追加できます。Amazon Cognito は、数百万のユーザーに対応してスケールし、SAML 2.0 や OpenID Connect などの規格を介して、Apple、Facebook、Google、Amazon などのソーシャル ID プロバイダーやエンタープライズ ID プロバイダーを使用したサインインをサポートします。</p>", "summary_detail": { "type": "text/html", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "<p>Amazon Cognito が欧州 (チューリッヒ) リージョンで利用可能になりました。Amazon Cognito では、ウェブアプリケーションやモバイルアプリケーションに認証、認可、ユーザー管理の機能を簡単に追加できます。Amazon Cognito は、数百万のユーザーに対応してスケールし、SAML 2.0 や OpenID Connect などの規格を介して、Apple、Facebook、Google、Amazon などのソーシャル ID プロバイダーやエンタープライズ ID プロバイダーを使用したサインインをサポートします。</p>" }, "published": "Fri, 22 Mar 2024 19:32:00 +0000", "published_parsed": [ 2024, 3, 22, 19, 32, 0, 4, 82, 0 ], "tags": [ { "term": "general:products/amazon-cognito,marketing:marchitecture/security-identity-and-compliance", "scheme": null, "label": null } ], "authors": [ { "email": "aws@amazon.com" } ], "author": "aws@amazon.com", "author_detail": { "email": "aws@amazon.com" } }, ######################## 行数多いので間を割愛 ######################## ], "feed": { "links": [ { "rel": "alternate", "type": "text/html", "href": "https://aws.amazon.com/jp/about-aws/whats-new/recent/" } ], "link": "https://aws.amazon.com/jp/about-aws/whats-new/recent/", "title": "最近の発表", "title_detail": { "type": "text/plain", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "最近の発表" }, "subtitle": "AWS クラウドプラットフォームは日々拡大しています。お知らせ、新製品の発表、ニュース、イノベーションなどの詳細をアマゾン ウェブ サービスでご確認ください。", "subtitle_detail": { "type": "text/html", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "AWS クラウドプラットフォームは日々拡大しています。お知らせ、新製品の発表、ニュース、イノベーションなどの詳細をアマゾン ウェブ サービスでご確認ください。" }, "authors": [ { "name": "Amazon Web Services", "email": "aws@amazon.com" } ], "author": "aws@amazon.com (Amazon Web Services)", "author_detail": { "name": "Amazon Web Services", "email": "aws@amazon.com" }, "updated": "Fri, 22 Mar 2024 19:33:37 +0000", "updated_parsed": [ 2024, 3, 22, 19, 33, 37, 4, 82, 0 ], "published": "Fri, 22 Mar 2024 19:33:37 +0000", "published_parsed": [ 2024, 3, 22, 19, 33, 37, 4, 82, 0 ], "docs": "http://blogs.law.harvard.edu/tech/rss", "image": { "href": "https://a0.awsstatic.com/main/images/logos/aws_logo_smile_179x109.png", "links": [ { "rel": "alternate", "type": "text/html", "href": "https://aws.amazon.com/jp/about-aws/whats-new/recent/" } ], "link": "https://aws.amazon.com/jp/about-aws/whats-new/recent/", "title": "最近の発表", "title_detail": { "type": "text/plain", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "最近の発表" }, "subtitle": "AWS クラウドプラットフォームは日々拡大しています。お知らせ、新製品の発表、ニュース、イノベーションなどの詳細をアマゾン ウェブ サービスでご確認ください。", "subtitle_detail": { "type": "text/html", "language": null, "base": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "value": "AWS クラウドプラットフォームは日々拡大しています。お知らせ、新製品の発表、ニュース、イノベーションなどの詳細をアマゾン ウェブ サービスでご確認ください。" } } }, "headers": { "content-type": "application/rss+xml;charset=UTF-8", "transfer-encoding": "chunked", "connection": "close", "server": "Server", "date": "Sat, 23 Mar 2024 03:35:31 GMT", "x-amz-rid": "6PBTC6Z0XPV7YBSP6A0J", "set-cookie": "aws-priv=eyJ2IjoxLCJldSI6MCwic3QiOjB9; Version=1; Comment=\"Anonymous cookie for privacy regulations\"; Domain=.aws.amazon.com; Max-Age=31536000; Expires=Sun, 23 Mar 2025 03:35:31 GMT; Path=/; Secure", "x-frame-options": "SAMEORIGIN", "x-xss-protection": "1; mode=block", "strict-transport-security": "max-age=63072000", "x-amz-id-1": "6PBTC6Z0XPV7YBSP6A0J", "last-modified": "Sat, 23 Mar 2024 02:57:54 GMT", "content-security-policy-report-only": "default-src *; connect-src *; font-src * data:; frame-src *; img-src * data:; media-src *; object-src *; script-src 'nonce-xsp7AIieRU2oQ8SMHCbQKA==' *; style-src 'unsafe-inline' *; report-uri https://prod-us-west-2.csp-report.marketing.aws.dev/submit", "x-content-type-options": "nosniff", "vary": "Content-Type,Accept-Encoding,User-Agent", "x-cache": "Miss from cloudfront", "via": "1.1 7637a60a07b64cdf45697b2f5cacacee.cloudfront.net (CloudFront)", "x-amz-cf-pop": "NRT57-P1", "x-amz-cf-id": "DFFSpWXI_mkCtGqBy_xI_7p1D7QlqZM01Dw5yyp2gIpec2X4B0hObw==" }, "updated": "Sat, 23 Mar 2024 02:57:54 GMT", "updated_parsed": [ 2024, 3, 23, 2, 57, 54, 5, 83, 0 ], "href": "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/", "status": 200, "encoding": "UTF-8", "version": "rss20", "namespaces": {} }
各記事に相当するentries
内に、published
という日時の項目があったため、これを使って通知する記事を抽出しようと思う。
Messaging API(LINE)
(Line Developersでアカウントつくって、チャネルつくって、といったところは割愛する。)
メッセージを送信する方法は5種類あるが、今回は友達登録者全員にメッセージを送るブロードキャストメッセージを採用することにする。
- 応答メッセージ
- プッシュメッセージ:1対1
- マルチキャストメッセージ:1対多(ユーザーID指定)
- ナローキャストメッセージ:1対多(絞り込み配信)
- ブロードキャストメッセージ:1対多(すべての友だち)
引用:メッセージの送信方法
https://developers.line.biz/ja/docs/messaging-api/sending-messages/
Messaging APIをPythonで利用するにあたっては、line-bot-sdk-pythonを利用する。
試しに動かしてみたところ、期待通りhogeというメッセージがLINEに届いた。
from linebot.v3 import ( WebhookHandler ) from linebot.v3.messaging import ( Configuration, ApiClient, MessagingApi, TextMessage, BroadcastRequest ) channel_access_token = "<自作したチャネルで発行したトークン>" channel_secret = "<自作したチャネルで発行したシークレット>" configuration = Configuration(access_token=channel_access_token) api_client = ApiClient(configuration) line_bot_api = MessagingApi(api_client) handler = WebhookHandler(channel_secret) line_bot_api.broadcast( BroadcastRequest( messages=[TextMessage(text="hoge")] ) )
なお、送信できるブロードキャストメッセージの上限は、60リクエスト/時ということを一応押さえておくと良い。(いろいろ試していたらこの上限にひっかかって処理に失敗し、Too Many Requestsが返ってきた、、)
参考:https://developers.line.biz/ja/reference/messaging-api/#rate-limits
つくってみた
イメージとしてはこんな感じ。
EventBridgeは、Schedulerを利用して、毎朝8時にLambdaを実行する。
Lambdaは、feedparserで取得した各記事のpublishedから、24時間以内に公開された記事がないか確認し、あればMessaging API(LINE)経由でLINE友達登録者にブロードキャストメッセージを送信する。
ディレクトリ構造
. ├── app.py ├── layer │ ├── python │ └── requirements.txt └── main.tf
app.py
import feedparser import os from datetime import datetime from linebot.v3 import ( WebhookHandler ) from linebot.v3.messaging import ( Configuration, ApiClient, MessagingApi, TextMessage, BroadcastRequest ) channel_access_token = os.getenv('CHANNEL_ACCESS_TOKEN') channel_secret = os.getenv('CHANNEL_SECRET') configuration = Configuration(access_token=channel_access_token) api_client = ApiClient(configuration) line_bot_api = MessagingApi(api_client) handler = WebhookHandler(channel_secret) now = datetime.now() # 対象RSS feeds = [ "https://aws.amazon.com/jp/about-aws/whats-new/recent/feed/" ] def lambda_handler(event, context): # FeedParserDictオブジェクトを取得 for feed in feeds: f = feedparser.parse(feed) # 各記事のlink、title、publishedを辞書に格納する dicts = [{'url': entry['link'], 'title': entry['title'], 'published': entry['published']} for entry in f['entries']] for dict in dicts: # publishedをstrからdatetime型へ変換 dict_pudlished = datetime.strptime(dict['published'],'%a, %d %b %Y %H:%M:%S %z') # https://docs.python.org/ja/3/library/datetime.html # publishedの日時が24時間以内であれば、該当記事のlinkとtitleをブロードキャストメッセージで送信 if now.timestamp() - dict_pudlished.timestamp() < 86400: line_bot_api.broadcast( BroadcastRequest( messages=[TextMessage(text = str(dict['title']) + str(dict['url']))] ) ) return "OK"
requirements.txt
line-bot-sdk==3.7.0 feedparser==6.0.11
main.tf
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.42" } } } provider "aws" { region = "ap-northeast-1" } # # eventbridge # # EventBridgeを定期実行するルールを定義 resource "aws_cloudwatch_event_rule" "rss" { name = "rss-by-terraform" schedule_expression = "cron(0 23 * * ? *)" # 毎日AM8:00 } # EventBridgeがキックするターゲットを定義 resource "aws_cloudwatch_event_target" "rss" { rule = aws_cloudwatch_event_rule.rss.name target_id = "rss-by-terraform" arn = aws_lambda_function.rss_function.arn } # # IAM # # LambdaがCloudWatch Logsにログを出力する権限を定義 resource "aws_iam_role" "iam_for_lambda" { name = "iam_for_lambda-by-terraform" assume_role_policy = data.aws_iam_policy_document.assume_role.json managed_policy_arns = [ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] } # LambdaがIAMロールを利用する権限を定義 data "aws_iam_policy_document" "assume_role" { statement { effect = "Allow" principals { type = "Service" identifiers = ["lambda.amazonaws.com"] } actions = ["sts:AssumeRole"] } } # # Layer # # 事前にリストアップした外部ライブラリをインストールする resource "null_resource" "rss_layer" { provisioner "local-exec" { command = "pip3 install -r layer/requirements.txt --target=layer/python" } } # Lambdaレイヤーに必要なものをzip化する data "archive_file" "rss_layer" { type = "zip" source_dir = "layer" output_path = "layer_by_tf.zip" } # Lambdaレイヤーを定義 resource "aws_lambda_layer_version" "rss_layer" { filename = data.archive_file.rss_layer.output_path layer_name = "rss_layer-by-terraform" source_code_hash = data.archive_file.rss_layer.output_base64sha256 compatible_runtimes = ["python3.12"] } # # Function # # Lambda関数に必要なものをzip化する data "archive_file" "rss_function" { type = "zip" source_file = "app.py" output_path = "app_by_tf.zip" } # Lambda関数を定義 resource "aws_lambda_function" "rss_function" { depends_on = [ aws_lambda_layer_version.rss_layer ] filename = data.archive_file.rss_function.output_path function_name = "rss-by-terraform" role = aws_iam_role.iam_for_lambda.arn handler = "app.lambda_handler" source_code_hash = data.archive_file.rss_function.output_base64sha256 runtime = "python3.12" timeout = 30 #seconds layers = [ aws_lambda_layer_version.rss_layer.arn ] environment { variables = { CHANNEL_SECRET = "hoge" CHANNEL_ACCESS_TOKEN = "hoge" } } lifecycle { ignore_changes = [ environment ] } } # EventBridgeがLambdaをキックする許可を定義 resource "aws_lambda_permission" "rss_function" { function_name = aws_lambda_function.rss_function.function_name action = "lambda:InvokeFunction" principal = "events.amazonaws.com" source_arn = aws_cloudwatch_event_rule.rss.arn } # アカウントIDなどを取得 data "aws_caller_identity" "current" {}