├── .drone.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── .perlcriticrc ├── .proverc ├── .shipit ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Changes ├── LICENSE ├── MANIFEST.SKIP ├── META.json ├── Makefile.PL ├── README.md ├── cpanfile ├── dist.ini ├── eg ├── echo.psgi ├── flex-message-showcases │ ├── appareal.json │ ├── hotel.json │ ├── local-search.json │ ├── menu.json │ ├── real-estate.json │ ├── receipt.json │ ├── restaurant.json │ ├── shopping.json │ ├── social.json │ ├── ticket.json │ ├── todo-app.json │ └── transit.json ├── get_number_of_messages.pl ├── line-bot-framework │ ├── bot-tmpl │ │ ├── help.txt │ │ └── memo.txt │ ├── cpanfile │ ├── interactive-bot.psgi │ └── lib │ │ ├── ExampleBot.pm │ │ ├── ExampleBot │ │ ├── Dispatcher.pm │ │ ├── Memo.pm │ │ └── Operation.pm │ │ ├── LINEBotFramework.pm │ │ └── LINEBotFramework │ │ ├── Dispatcher.pm │ │ ├── Request.pm │ │ └── Response.pm ├── push-flex-message-showcases.pl └── push_imagemap-template.pl ├── lib └── LINE │ └── Bot │ ├── API.pm │ ├── API │ ├── Builder │ │ ├── FlexMessage.pm │ │ ├── ImagemapMessage.pm │ │ ├── SendMessage.pm │ │ └── TemplateMessage.pm │ ├── Client.pm │ ├── Client │ │ └── Furl.pm │ ├── Event.pm │ ├── Event │ │ ├── AccountLink.pm │ │ ├── Base.pm │ │ ├── BeaconDetection.pm │ │ ├── Follow.pm │ │ ├── Join.pm │ │ ├── Leave.pm │ │ ├── MemberJoin.pm │ │ ├── MemberLeave.pm │ │ ├── Message.pm │ │ ├── Postback.pm │ │ ├── Things.pm │ │ ├── Unfollow.pm │ │ ├── Unknown.pm │ │ ├── Unsend.pm │ │ └── VideoViewingComplete.pm │ ├── Response │ │ ├── AudienceAuthorityLevel.pm │ │ ├── AudienceData.pm │ │ ├── AudienceGroupForClickRetargeting.pm │ │ ├── AudienceGroupForImpressionRetargeting.pm │ │ ├── AudienceGroupForUploadingUserId.pm │ │ ├── AudienceMultipleData.pm │ │ ├── BotInfo.pm │ │ ├── Common.pm │ │ ├── Content.pm │ │ ├── Count.pm │ │ ├── Error.pm │ │ ├── Followers.pm │ │ ├── FriendDemographics.pm │ │ ├── GroupMemberProfile.pm │ │ ├── GroupSummary.pm │ │ ├── IssueLinkToken.pm │ │ ├── NarrowcastStatus.pm │ │ ├── NumberOfFollowers.pm │ │ ├── NumberOfMessageDeliveries.pm │ │ ├── NumberOfSentMessages.pm │ │ ├── Profile.pm │ │ ├── RichMenu.pm │ │ ├── RichMenuList.pm │ │ ├── RoomMemberProfile.pm │ │ ├── TargetLimit.pm │ │ ├── Token.pm │ │ ├── TotalUsage.pm │ │ ├── UserInteractionStatistics.pm │ │ ├── WebhookInformation.pm │ │ └── WebhookTest.pm │ └── Types.pm │ ├── Audience.pm │ └── Message │ └── Narrowcast.pm └── t ├── 00_compile.t ├── 00_send_message_response.t ├── 01_check_sender.t ├── 01_send_text-with-emoji.t ├── 01_send_text.t ├── 02_send_image.t ├── 03_send_video.t ├── 04_send_audio.t ├── 05_send_location.t ├── 06_send_sticker.t ├── 07_send_imagemap.t ├── 08_send_template.t ├── 11_send_multiple.t ├── 12_get_number_of_send_messages.t ├── 12_multicast.t ├── 13_broadcast.t ├── 14_get_friend_demographics.t ├── 14_get_number_of_message_deliveries.t ├── 14_get_number_of_messages_sent_this_month.t ├── 14_get_user_interaction_statistics.t ├── 15_get_target_limit_for_additional_messages.t ├── 16_create_audience_for_click_based_retargeting.t ├── 16_create_audience_for_impression_based_retargeting.t ├── 16_create_audience_for_uploading.t ├── 16_delete_audience.t ├── 16_get_audience_data.t ├── 16_get_authority_level.t ├── 16_get_data_for_multiple_audience.t ├── 16_rename_audience.t ├── 16_update_authority_level.t ├── 17_get_narrowcast_message_status.t ├── 17_send_message.t ├── 21_get_profile.t ├── 22_get_group_member_profile.t ├── 22_get_group_summary.t ├── 22_get_member_in_group_count.t ├── 22_get_member_in_room_count.t ├── 22_get_room_member_profile.t ├── 41_event.t ├── 42_create_events_by_api.t ├── 51_issue_link_token.t ├── 52_download_rich_menu_image.t ├── 52_rich_menu.t ├── 52_rich_menu_response.t ├── 52_upload_rich_menu_image.t ├── 53-retry_key.t ├── 54-get_bot_info.t ├── 55-get-webhook-endpoint-information.t ├── 55-set-webhook-url.t ├── 55-test-webhook.t ├── 56_get_followers.t ├── 57_validate_message_objects.t ├── PR75-get-number-of-followers.t ├── controller-rich-menu-image-sample.jpg ├── examples ├── account-link-event.json ├── audio-message-1.json ├── beacon-event.json ├── device-link-event.json ├── device-unlink-event.json ├── error-response-1.json ├── follow-event.json ├── image-message-1.json ├── join-event.json ├── leave-event.json ├── member-joined-event.json ├── member-left-event.json ├── postback-event.json ├── text-message-1.json ├── unfollow-event.json ├── unsend-event.json ├── video-message-1.json └── video-viewing-complete-event.json ├── lib └── t │ └── Util.pm ├── oauth.t └── types-validate-examples.t /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | name: default 3 | 4 | steps: 5 | - name: Test 6 | image: gugod/perl-testbox:5.28 7 | commands: 8 | - cpanm --quiet --installdeps . 9 | - cpanm --quiet -n Test2::Harness 10 | - yath --qvf 11 | 12 | - name: Build 13 | image: gugod/perl-testbox:5.28 14 | depends_on: 15 | - Test 16 | commands: 17 | - cpanm --quiet -n Dist::Milla 18 | - dzil authordeps --missing | cpanm --quiet -n 19 | - milla build 20 | - ls -l 21 | 22 | - &installation 23 | name: Install (perl 5.30) 24 | image: perl:5.30 25 | depends_on: 26 | - Build 27 | commands: 28 | - echo *.tar.gz 29 | - cpanm -q *.tar.gz 30 | 31 | - << : *installation 32 | name: Install (perl 5.28) 33 | image: perl:5.28 34 | 35 | - << : *installation 36 | name: Install (perl 5.26) 37 | image: perl:5.26 38 | 39 | - << : *installation 40 | name: Install (perl 5.24) 41 | image: perl:5.24 42 | 43 | - << : *installation 44 | name: Install (perl 5.22) 45 | image: perl:5.22 46 | 47 | - << : *installation 48 | name: Install (perl 5.20) 49 | image: perl:5.20 50 | 51 | - << : *installation 52 | name: Install (perl 5.18) 53 | image: perl:5.18 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. ... 16 | 2. ??? 17 | 3. See error message 18 | 19 | **Expected behavior** 20 | No errors, or expecting error but none shows up. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Information about you env** 26 | 27 | - OS and Version (The output `uname -a` would be good.) 28 | - perl version (abridged version of `perl -V`, if possible) 29 | - SDK version 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: perlcritic 14 | uses: gugod/actions-perlcritic@v1.0 15 | with: 16 | paths: "lib eg t" 17 | 18 | test: 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os: ['ubuntu-22.04', 'ubuntu-20.04'] 23 | perl: [ '5.32', '5.30' ] 24 | name: Test Perl ${{ matrix.perl }} on ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Set up perl 28 | uses: shogo82148/actions-setup-perl@v1 29 | with: 30 | perl-version: ${{ matrix.perl }} 31 | - run: perl -V 32 | - run: cpanm -q --notest --installdeps . 33 | - run: yath test --qvf t/ 34 | 35 | milla-build: 36 | name: Build tarball with milla 37 | runs-on: ubuntu-22.04 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Set up perl 41 | uses: shogo82148/actions-setup-perl@v1 42 | with: 43 | perl-version: 5.32 44 | - run: cpanm -q --notest Dist::Milla LWP::Protocol::https Dist::Zilla::Plugin::Test::Perl::Critic Test::Pod JSON::XS Furl::HTTP Test2::V0 45 | - run: cpanm -q --notest --installdeps . 46 | - run: milla build 47 | - run: ls -l LINE-Bot-API*.tar.gz 48 | - run: tar tvzf LINE-Bot-API*.tar.gz 49 | - run: cpanm --verbose LINE-Bot-API*.tar.gz 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /LINE-Bot-API-* 2 | /.build 3 | /_build* 4 | /Build 5 | MYMETA.* 6 | !META.json 7 | blib 8 | *.swp 9 | -------------------------------------------------------------------------------- /.perlcriticrc: -------------------------------------------------------------------------------- 1 | color = 0 2 | verbose = %f:%l:%c:[%p] %m => %r\n 3 | 4 | only = 1 5 | include = RequireUseStrict ProhibitUnusedVariables ProhibitUnreachableCode ProhibitUnusedConstant ProhibitUnusedInclude ProhibitUnusedImport ProhibitDuplicateHashKeys ProhibitExcessiveColons ProhibitDuplicatedSub ProhibitTrailingWhitespace 6 | 7 | [TestingAndDebugging::RequireUseStrict] 8 | equivalent_modules = Test2::V0 9 | -------------------------------------------------------------------------------- /.proverc: -------------------------------------------------------------------------------- 1 | -I t/lib 2 | 3 | 4 | -------------------------------------------------------------------------------- /.shipit: -------------------------------------------------------------------------------- 1 | steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist, UploadCPAN 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dl_oss_dev@linecorp.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to LINE Bot SDK for Perl project 2 | 3 | First of all, thank you so much for taking your time to contribute! LINE Bot SDK for Perl is not very different from any other open 4 | source projects you are aware of. It will be amazing if you could help us by doing any of the following: 5 | 6 | - File an issue in [the issue tracker](https://github.com/line/line-bot-sdk-perl/issues) to report bugs and propose new features and 7 | improvements. 8 | - Ask a question using [the issue tracker](https://github.com/line/line-bot-sdk-perl/issues). 9 | - Contribute your work by sending [a pull request](https://github.com/line/line-bot-sdk-perl/pulls). 10 | - Please also update file `Changes` for significant changes in PR. 11 | 12 | ### Contributor license agreement 13 | 14 | When you are sending a pull request and it's a non-trivial change beyond fixing typos, please make sure to sign 15 | [the ICLA (individual contributor license agreement)](https://cla-assistant.io/line/line-bot-sdk-perl). Please 16 | [contact us](mailto:dl_oss_dev@linecorp.com) if you need the CCLA (corporate contributor license agreement). 17 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | \bRCS\b 2 | \bCVS\b 3 | ^MANIFEST\. 4 | ^MYMETA\. 5 | ^Makefile$ 6 | ~$ 7 | ^# 8 | \.old$ 9 | ^blib/ 10 | ^pm_to_blib 11 | ^MakeMaker-\d 12 | \.gz$ 13 | \.cvsignore 14 | ^t/9\d_.*\.t 15 | ^t/perlcritic 16 | ^tools/ 17 | \.svn/ 18 | ^[^/]+\.yaml$ 19 | ^[^/]+\.pl$ 20 | ^\.shipit$ 21 | ^\.git/ 22 | \.sw[po]$ 23 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "SDK of the LINE Messaging API for Perl", 3 | "author" : [ 4 | "LINE Corporation" 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Dist::Milla version v1.0.22, Dist::Zilla version 6.032, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "artistic_2" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "LINE-Bot-API", 16 | "no_index" : { 17 | "directory" : [ 18 | "eg", 19 | "examples", 20 | "inc", 21 | "share", 22 | "t", 23 | "xt" 24 | ] 25 | }, 26 | "prereqs" : { 27 | "configure" : { 28 | "requires" : { 29 | "ExtUtils::MakeMaker" : "0" 30 | } 31 | }, 32 | "develop" : { 33 | "requires" : { 34 | "Dist::Milla" : "v1.0.22", 35 | "Test::Perl::Critic" : "0", 36 | "Test::Pod" : "1.41" 37 | } 38 | }, 39 | "runtime" : { 40 | "requires" : { 41 | "Digest::SHA" : "0", 42 | "Furl" : "0", 43 | "IO::Socket::SSL" : "2.060", 44 | "JSON::XS" : "0", 45 | "MIME::Base64" : "0", 46 | "Type::Tiny" : "1.004000", 47 | "URI" : "0", 48 | "URI::QueryParam" : "0", 49 | "parent" : "0", 50 | "perl" : "5.014000" 51 | } 52 | }, 53 | "test" : { 54 | "requires" : { 55 | "App::Yath" : "0", 56 | "Test2::V0" : "0", 57 | "Test::More" : "0" 58 | } 59 | } 60 | }, 61 | "release_status" : "stable", 62 | "resources" : { 63 | "bugtracker" : { 64 | "web" : "https://github.com/line/line-bot-sdk-perl/issues" 65 | }, 66 | "homepage" : "https://github.com/line/line-bot-sdk-perl", 67 | "repository" : { 68 | "type" : "git", 69 | "url" : "https://github.com/line/line-bot-sdk-perl.git", 70 | "web" : "https://github.com/line/line-bot-sdk-perl" 71 | } 72 | }, 73 | "version" : "1.21", 74 | "x_contributors" : [ 75 | "7kashin <4nji.7ka6ra@gmail.com>", 76 | "bedoshi ", 77 | "Hirofumi Kataoka ", 78 | "Hiroki Nishino <7100187+tawAsh1@users.noreply.github.com>", 79 | "Hiroki Nishino ", 80 | "Hiroyuki Akabane ", 81 | "Jose Luis ", 82 | "Kang-min Liu ", 83 | "Kang-min Liu ", 84 | "Kang-min Liu ", 85 | "Kazuhiro Osawa ", 86 | "Kazuki Nakajima ", 87 | "kenneth-lau ", 88 | "kzwmsyk ", 89 | "LAU KENNETH ", 90 | "moznion ", 91 | "Ned Hoy ", 92 | "Seo-yeon Lee ", 93 | "Syohei YOSHIDA ", 94 | "Tokuhiro Matsuno ", 95 | "Yuki Yoshida ", 96 | "Yuta Kasai " 97 | ], 98 | "x_generated_by_perl" : "v5.40.0", 99 | "x_serialization_backend" : "Cpanel::JSON::XS version 4.38", 100 | "x_spdx_expression" : "Artistic-2.0", 101 | "x_static_install" : 1 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.032. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.014000; 6 | 7 | use ExtUtils::MakeMaker; 8 | 9 | my %WriteMakefileArgs = ( 10 | "ABSTRACT" => "SDK of the LINE Messaging API for Perl", 11 | "AUTHOR" => "LINE Corporation", 12 | "CONFIGURE_REQUIRES" => { 13 | "ExtUtils::MakeMaker" => 0 14 | }, 15 | "DISTNAME" => "LINE-Bot-API", 16 | "LICENSE" => "artistic_2", 17 | "MIN_PERL_VERSION" => "5.014000", 18 | "NAME" => "LINE::Bot::API", 19 | "PREREQ_PM" => { 20 | "Digest::SHA" => 0, 21 | "Furl" => 0, 22 | "IO::Socket::SSL" => "2.060", 23 | "JSON::XS" => 0, 24 | "MIME::Base64" => 0, 25 | "Type::Tiny" => "1.004000", 26 | "URI" => 0, 27 | "URI::QueryParam" => 0, 28 | "parent" => 0 29 | }, 30 | "TEST_REQUIRES" => { 31 | "App::Yath" => 0, 32 | "Test2::V0" => 0, 33 | "Test::More" => 0 34 | }, 35 | "VERSION" => "1.21", 36 | "test" => { 37 | "TESTS" => "t/*.t" 38 | } 39 | ); 40 | 41 | 42 | my %FallbackPrereqs = ( 43 | "App::Yath" => 0, 44 | "Digest::SHA" => 0, 45 | "Furl" => 0, 46 | "IO::Socket::SSL" => "2.060", 47 | "JSON::XS" => 0, 48 | "MIME::Base64" => 0, 49 | "Test2::V0" => 0, 50 | "Test::More" => 0, 51 | "Type::Tiny" => "1.004000", 52 | "URI" => 0, 53 | "URI::QueryParam" => 0, 54 | "parent" => 0 55 | ); 56 | 57 | 58 | unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { 59 | delete $WriteMakefileArgs{TEST_REQUIRES}; 60 | delete $WriteMakefileArgs{BUILD_REQUIRES}; 61 | $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; 62 | } 63 | 64 | delete $WriteMakefileArgs{CONFIGURE_REQUIRES} 65 | unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; 66 | 67 | WriteMakefile(%WriteMakefileArgs); 68 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl', '5.014000'; 2 | 3 | requires 'parent'; 4 | requires 'Digest::SHA'; 5 | requires 'Furl'; 6 | requires 'JSON::XS'; 7 | requires 'IO::Socket::SSL', '2.060'; 8 | requires 'MIME::Base64'; 9 | requires 'Type::Tiny', '1.004000'; 10 | requires 'URI'; 11 | requires 'URI::QueryParam'; 12 | 13 | on test => sub { 14 | requires 'App::Yath'; 15 | requires 'Test2::V0'; 16 | requires 'Test::More'; 17 | }; 18 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = LINE-Bot-API 2 | author = LINE Corporation 3 | license = Artistic_2_0 4 | 5 | [@Milla] 6 | installer = MakeMaker 7 | 8 | [Test::Perl::Critic] 9 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/appareal.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "body": { 4 | "type": "box", 5 | "layout": "vertical", 6 | "contents": [ 7 | { 8 | "type": "image", 9 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip3.jpg", 10 | "size": "full", 11 | "aspectMode": "cover", 12 | "aspectRatio": "1:1", 13 | "gravity": "center" 14 | }, 15 | { 16 | "type": "image", 17 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip15.png", 18 | "position": "absolute", 19 | "aspectMode": "fit", 20 | "aspectRatio": "1:1", 21 | "offsetTop": "0px", 22 | "offsetBottom": "0px", 23 | "offsetStart": "0px", 24 | "offsetEnd": "0px", 25 | "size": "full" 26 | }, 27 | { 28 | "type": "box", 29 | "layout": "horizontal", 30 | "contents": [ 31 | { 32 | "type": "box", 33 | "layout": "vertical", 34 | "contents": [ 35 | { 36 | "type": "box", 37 | "layout": "horizontal", 38 | "contents": [ 39 | { 40 | "type": "text", 41 | "text": "Brown Grand Hotel", 42 | "size": "xl", 43 | "color": "#ffffff" 44 | } 45 | ] 46 | }, 47 | { 48 | "type": "box", 49 | "layout": "baseline", 50 | "contents": [ 51 | { 52 | "type": "icon", 53 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 54 | }, 55 | { 56 | "type": "icon", 57 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 58 | }, 59 | { 60 | "type": "icon", 61 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 62 | }, 63 | { 64 | "type": "icon", 65 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 66 | }, 67 | { 68 | "type": "icon", 69 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png" 70 | }, 71 | { 72 | "type": "text", 73 | "text": "4.0", 74 | "color": "#a9a9a9" 75 | } 76 | ], 77 | "spacing": "xs" 78 | }, 79 | { 80 | "type": "box", 81 | "layout": "horizontal", 82 | "contents": [ 83 | { 84 | "type": "box", 85 | "layout": "baseline", 86 | "contents": [ 87 | { 88 | "type": "text", 89 | "text": "¥62,000", 90 | "color": "#ffffff", 91 | "size": "md", 92 | "flex": 0, 93 | "align": "end" 94 | }, 95 | { 96 | "type": "text", 97 | "text": "¥82,000", 98 | "color": "#a9a9a9", 99 | "decoration": "line-through", 100 | "size": "sm", 101 | "align": "end" 102 | } 103 | ], 104 | "flex": 0, 105 | "spacing": "lg" 106 | } 107 | ] 108 | } 109 | ], 110 | "spacing": "xs" 111 | } 112 | ], 113 | "position": "absolute", 114 | "offsetBottom": "0px", 115 | "offsetStart": "0px", 116 | "offsetEnd": "0px", 117 | "paddingAll": "20px" 118 | } 119 | ], 120 | "paddingAll": "0px" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/hotel.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "body": { 4 | "type": "box", 5 | "layout": "vertical", 6 | "contents": [ 7 | { 8 | "type": "image", 9 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip3.jpg", 10 | "size": "full", 11 | "aspectMode": "cover", 12 | "aspectRatio": "1:1", 13 | "gravity": "center" 14 | }, 15 | { 16 | "type": "image", 17 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip15.png", 18 | "position": "absolute", 19 | "aspectMode": "fit", 20 | "aspectRatio": "1:1", 21 | "offsetTop": "0px", 22 | "offsetBottom": "0px", 23 | "offsetStart": "0px", 24 | "offsetEnd": "0px", 25 | "size": "full" 26 | }, 27 | { 28 | "type": "box", 29 | "layout": "horizontal", 30 | "contents": [ 31 | { 32 | "type": "box", 33 | "layout": "vertical", 34 | "contents": [ 35 | { 36 | "type": "box", 37 | "layout": "horizontal", 38 | "contents": [ 39 | { 40 | "type": "text", 41 | "text": "Brown Grand Hotel", 42 | "size": "xl", 43 | "color": "#ffffff" 44 | } 45 | ] 46 | }, 47 | { 48 | "type": "box", 49 | "layout": "baseline", 50 | "contents": [ 51 | { 52 | "type": "icon", 53 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 54 | }, 55 | { 56 | "type": "icon", 57 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 58 | }, 59 | { 60 | "type": "icon", 61 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 62 | }, 63 | { 64 | "type": "icon", 65 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 66 | }, 67 | { 68 | "type": "icon", 69 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png" 70 | }, 71 | { 72 | "type": "text", 73 | "text": "4.0", 74 | "color": "#a9a9a9" 75 | } 76 | ], 77 | "spacing": "xs" 78 | }, 79 | { 80 | "type": "box", 81 | "layout": "horizontal", 82 | "contents": [ 83 | { 84 | "type": "box", 85 | "layout": "baseline", 86 | "contents": [ 87 | { 88 | "type": "text", 89 | "text": "¥62,000", 90 | "color": "#ffffff", 91 | "size": "md", 92 | "flex": 0, 93 | "align": "end" 94 | }, 95 | { 96 | "type": "text", 97 | "text": "¥82,000", 98 | "color": "#a9a9a9", 99 | "decoration": "line-through", 100 | "size": "sm", 101 | "align": "end" 102 | } 103 | ], 104 | "flex": 0, 105 | "spacing": "lg" 106 | } 107 | ] 108 | } 109 | ], 110 | "spacing": "xs" 111 | } 112 | ], 113 | "position": "absolute", 114 | "offsetBottom": "0px", 115 | "offsetStart": "0px", 116 | "offsetEnd": "0px", 117 | "paddingAll": "20px" 118 | } 119 | ], 120 | "paddingAll": "0px" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "hero": { 4 | "type": "image", 5 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_2_restaurant.png", 6 | "size": "full", 7 | "aspectRatio": "20:13", 8 | "aspectMode": "cover", 9 | "action": { 10 | "type": "uri", 11 | "uri": "https://linecorp.com" 12 | } 13 | }, 14 | "body": { 15 | "type": "box", 16 | "layout": "vertical", 17 | "spacing": "md", 18 | "action": { 19 | "type": "uri", 20 | "uri": "https://linecorp.com" 21 | }, 22 | "contents": [ 23 | { 24 | "type": "text", 25 | "text": "Brown's Burger", 26 | "size": "xl", 27 | "weight": "bold" 28 | }, 29 | { 30 | "type": "box", 31 | "layout": "vertical", 32 | "spacing": "sm", 33 | "contents": [ 34 | { 35 | "type": "box", 36 | "layout": "baseline", 37 | "contents": [ 38 | { 39 | "type": "icon", 40 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/restaurant_regular_32.png" 41 | }, 42 | { 43 | "type": "text", 44 | "text": "$10.5", 45 | "weight": "bold", 46 | "margin": "sm", 47 | "flex": 0 48 | }, 49 | { 50 | "type": "text", 51 | "text": "400kcl", 52 | "size": "sm", 53 | "align": "end", 54 | "color": "#aaaaaa" 55 | } 56 | ] 57 | }, 58 | { 59 | "type": "box", 60 | "layout": "baseline", 61 | "contents": [ 62 | { 63 | "type": "icon", 64 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/restaurant_large_32.png" 65 | }, 66 | { 67 | "type": "text", 68 | "text": "$15.5", 69 | "weight": "bold", 70 | "margin": "sm", 71 | "flex": 0 72 | }, 73 | { 74 | "type": "text", 75 | "text": "550kcl", 76 | "size": "sm", 77 | "align": "end", 78 | "color": "#aaaaaa" 79 | } 80 | ] 81 | } 82 | ] 83 | }, 84 | { 85 | "type": "text", 86 | "text": "Sauce, Onions, Pickles, Lettuce & Cheese", 87 | "wrap": true, 88 | "color": "#aaaaaa", 89 | "size": "xxs" 90 | } 91 | ] 92 | }, 93 | "footer": { 94 | "type": "box", 95 | "layout": "vertical", 96 | "contents": [ 97 | { 98 | "type": "spacer", 99 | "size": "xxl" 100 | }, 101 | { 102 | "type": "button", 103 | "style": "primary", 104 | "color": "#905c44", 105 | "action": { 106 | "type": "uri", 107 | "label": "Add to Cart", 108 | "uri": "https://linecorp.com" 109 | } 110 | } 111 | ] 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/real-estate.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "header": { 4 | "type": "box", 5 | "layout": "vertical", 6 | "contents": [ 7 | { 8 | "type": "box", 9 | "layout": "horizontal", 10 | "contents": [ 11 | { 12 | "type": "image", 13 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip4.jpg", 14 | "size": "full", 15 | "aspectMode": "cover", 16 | "aspectRatio": "150:196", 17 | "gravity": "center", 18 | "flex": 1 19 | }, 20 | { 21 | "type": "box", 22 | "layout": "vertical", 23 | "contents": [ 24 | { 25 | "type": "image", 26 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip5.jpg", 27 | "size": "full", 28 | "aspectMode": "cover", 29 | "aspectRatio": "150:98", 30 | "gravity": "center" 31 | }, 32 | { 33 | "type": "image", 34 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip6.jpg", 35 | "size": "full", 36 | "aspectMode": "cover", 37 | "aspectRatio": "150:98", 38 | "gravity": "center" 39 | } 40 | ], 41 | "flex": 1 42 | }, 43 | { 44 | "type": "box", 45 | "layout": "horizontal", 46 | "contents": [ 47 | { 48 | "type": "text", 49 | "text": "NEW", 50 | "size": "xs", 51 | "color": "#ffffff", 52 | "align": "center", 53 | "gravity": "center" 54 | } 55 | ], 56 | "backgroundColor": "#EC3D44", 57 | "paddingAll": "2px", 58 | "paddingStart": "4px", 59 | "paddingEnd": "4px", 60 | "flex": 0, 61 | "position": "absolute", 62 | "offsetStart": "18px", 63 | "offsetTop": "18px", 64 | "cornerRadius": "100px", 65 | "width": "48px", 66 | "height": "25px" 67 | } 68 | ] 69 | } 70 | ], 71 | "paddingAll": "0px" 72 | }, 73 | "body": { 74 | "type": "box", 75 | "layout": "vertical", 76 | "contents": [ 77 | { 78 | "type": "box", 79 | "layout": "vertical", 80 | "contents": [ 81 | { 82 | "type": "box", 83 | "layout": "vertical", 84 | "contents": [ 85 | { 86 | "type": "text", 87 | "contents": [], 88 | "size": "xl", 89 | "wrap": true, 90 | "text": "Cony Residence", 91 | "color": "#ffffff", 92 | "weight": "bold" 93 | }, 94 | { 95 | "type": "text", 96 | "text": "3 Bedrooms, ¥35,000", 97 | "color": "#ffffffcc", 98 | "size": "sm" 99 | } 100 | ], 101 | "spacing": "sm" 102 | }, 103 | { 104 | "type": "box", 105 | "layout": "vertical", 106 | "contents": [ 107 | { 108 | "type": "box", 109 | "layout": "vertical", 110 | "contents": [ 111 | { 112 | "type": "text", 113 | "contents": [], 114 | "size": "sm", 115 | "wrap": true, 116 | "margin": "lg", 117 | "color": "#ffffffde", 118 | "text": "Private Pool, Delivery box, Floor heating, Private Cinema" 119 | } 120 | ] 121 | } 122 | ], 123 | "paddingAll": "13px", 124 | "backgroundColor": "#ffffff1A", 125 | "cornerRadius": "2px", 126 | "margin": "xl" 127 | } 128 | ] 129 | } 130 | ], 131 | "paddingAll": "20px", 132 | "backgroundColor": "#464F69" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/restaurant.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "hero": { 4 | "type": "image", 5 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png", 6 | "size": "full", 7 | "aspectRatio": "20:13", 8 | "aspectMode": "cover", 9 | "action": { 10 | "type": "uri", 11 | "uri": "http://linecorp.com/" 12 | } 13 | }, 14 | "body": { 15 | "type": "box", 16 | "layout": "vertical", 17 | "contents": [ 18 | { 19 | "type": "text", 20 | "text": "Brown Cafe", 21 | "weight": "bold", 22 | "size": "xl" 23 | }, 24 | { 25 | "type": "box", 26 | "layout": "baseline", 27 | "margin": "md", 28 | "contents": [ 29 | { 30 | "type": "icon", 31 | "size": "sm", 32 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 33 | }, 34 | { 35 | "type": "icon", 36 | "size": "sm", 37 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 38 | }, 39 | { 40 | "type": "icon", 41 | "size": "sm", 42 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 43 | }, 44 | { 45 | "type": "icon", 46 | "size": "sm", 47 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 48 | }, 49 | { 50 | "type": "icon", 51 | "size": "sm", 52 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png" 53 | }, 54 | { 55 | "type": "text", 56 | "text": "4.0", 57 | "size": "sm", 58 | "color": "#999999", 59 | "margin": "md", 60 | "flex": 0 61 | } 62 | ] 63 | }, 64 | { 65 | "type": "box", 66 | "layout": "vertical", 67 | "margin": "lg", 68 | "spacing": "sm", 69 | "contents": [ 70 | { 71 | "type": "box", 72 | "layout": "baseline", 73 | "spacing": "sm", 74 | "contents": [ 75 | { 76 | "type": "text", 77 | "text": "Place", 78 | "color": "#aaaaaa", 79 | "size": "sm", 80 | "flex": 1 81 | }, 82 | { 83 | "type": "text", 84 | "text": "Miraina Tower, 4-1-6 Shinjuku, Tokyo", 85 | "wrap": true, 86 | "color": "#666666", 87 | "size": "sm", 88 | "flex": 5 89 | } 90 | ] 91 | }, 92 | { 93 | "type": "box", 94 | "layout": "baseline", 95 | "spacing": "sm", 96 | "contents": [ 97 | { 98 | "type": "text", 99 | "text": "Time", 100 | "color": "#aaaaaa", 101 | "size": "sm", 102 | "flex": 1 103 | }, 104 | { 105 | "type": "text", 106 | "text": "10:00 - 23:00", 107 | "wrap": true, 108 | "color": "#666666", 109 | "size": "sm", 110 | "flex": 5 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | ] 117 | }, 118 | "footer": { 119 | "type": "box", 120 | "layout": "vertical", 121 | "spacing": "sm", 122 | "contents": [ 123 | { 124 | "type": "button", 125 | "style": "link", 126 | "height": "sm", 127 | "action": { 128 | "type": "uri", 129 | "label": "CALL", 130 | "uri": "https://linecorp.com" 131 | } 132 | }, 133 | { 134 | "type": "button", 135 | "style": "link", 136 | "height": "sm", 137 | "action": { 138 | "type": "uri", 139 | "label": "WEBSITE", 140 | "uri": "https://linecorp.com" 141 | } 142 | }, 143 | { 144 | "type": "spacer", 145 | "size": "sm" 146 | } 147 | ], 148 | "flex": 0 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/shopping.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "carousel", 3 | "contents": [ 4 | { 5 | "type": "bubble", 6 | "hero": { 7 | "type": "image", 8 | "size": "full", 9 | "aspectRatio": "20:13", 10 | "aspectMode": "cover", 11 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_5_carousel.png" 12 | }, 13 | "body": { 14 | "type": "box", 15 | "layout": "vertical", 16 | "spacing": "sm", 17 | "contents": [ 18 | { 19 | "type": "text", 20 | "text": "Arm Chair, White", 21 | "wrap": true, 22 | "weight": "bold", 23 | "size": "xl" 24 | }, 25 | { 26 | "type": "box", 27 | "layout": "baseline", 28 | "contents": [ 29 | { 30 | "type": "text", 31 | "text": "$49", 32 | "wrap": true, 33 | "weight": "bold", 34 | "size": "xl", 35 | "flex": 0 36 | }, 37 | { 38 | "type": "text", 39 | "text": ".99", 40 | "wrap": true, 41 | "weight": "bold", 42 | "size": "sm", 43 | "flex": 0 44 | } 45 | ] 46 | } 47 | ] 48 | }, 49 | "footer": { 50 | "type": "box", 51 | "layout": "vertical", 52 | "spacing": "sm", 53 | "contents": [ 54 | { 55 | "type": "button", 56 | "style": "primary", 57 | "action": { 58 | "type": "uri", 59 | "label": "Add to Cart", 60 | "uri": "https://linecorp.com" 61 | } 62 | }, 63 | { 64 | "type": "button", 65 | "action": { 66 | "type": "uri", 67 | "label": "Add to wishlist", 68 | "uri": "https://linecorp.com" 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | { 75 | "type": "bubble", 76 | "hero": { 77 | "type": "image", 78 | "size": "full", 79 | "aspectRatio": "20:13", 80 | "aspectMode": "cover", 81 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_6_carousel.png" 82 | }, 83 | "body": { 84 | "type": "box", 85 | "layout": "vertical", 86 | "spacing": "sm", 87 | "contents": [ 88 | { 89 | "type": "text", 90 | "text": "Metal Desk Lamp", 91 | "wrap": true, 92 | "weight": "bold", 93 | "size": "xl" 94 | }, 95 | { 96 | "type": "box", 97 | "layout": "baseline", 98 | "flex": 1, 99 | "contents": [ 100 | { 101 | "type": "text", 102 | "text": "$11", 103 | "wrap": true, 104 | "weight": "bold", 105 | "size": "xl", 106 | "flex": 0 107 | }, 108 | { 109 | "type": "text", 110 | "text": ".99", 111 | "wrap": true, 112 | "weight": "bold", 113 | "size": "sm", 114 | "flex": 0 115 | } 116 | ] 117 | }, 118 | { 119 | "type": "text", 120 | "text": "Temporarily out of stock", 121 | "wrap": true, 122 | "size": "xxs", 123 | "margin": "md", 124 | "color": "#ff5551", 125 | "flex": 0 126 | } 127 | ] 128 | }, 129 | "footer": { 130 | "type": "box", 131 | "layout": "vertical", 132 | "spacing": "sm", 133 | "contents": [ 134 | { 135 | "type": "button", 136 | "flex": 2, 137 | "style": "primary", 138 | "color": "#aaaaaa", 139 | "action": { 140 | "type": "uri", 141 | "label": "Add to Cart", 142 | "uri": "https://linecorp.com" 143 | } 144 | }, 145 | { 146 | "type": "button", 147 | "action": { 148 | "type": "uri", 149 | "label": "Add to wish list", 150 | "uri": "https://linecorp.com" 151 | } 152 | } 153 | ] 154 | } 155 | }, 156 | { 157 | "type": "bubble", 158 | "body": { 159 | "type": "box", 160 | "layout": "vertical", 161 | "spacing": "sm", 162 | "contents": [ 163 | { 164 | "type": "button", 165 | "flex": 1, 166 | "gravity": "center", 167 | "action": { 168 | "type": "uri", 169 | "label": "See more", 170 | "uri": "https://linecorp.com" 171 | } 172 | } 173 | ] 174 | } 175 | } 176 | ] 177 | } 178 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/social.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "body": { 4 | "type": "box", 5 | "layout": "vertical", 6 | "contents": [ 7 | { 8 | "type": "box", 9 | "layout": "horizontal", 10 | "contents": [ 11 | { 12 | "type": "image", 13 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip7.jpg", 14 | "size": "5xl", 15 | "aspectMode": "cover", 16 | "aspectRatio": "150:196", 17 | "gravity": "center", 18 | "flex": 1 19 | }, 20 | { 21 | "type": "box", 22 | "layout": "vertical", 23 | "contents": [ 24 | { 25 | "type": "image", 26 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip8.jpg", 27 | "size": "full", 28 | "aspectMode": "cover", 29 | "aspectRatio": "150:98", 30 | "gravity": "center" 31 | }, 32 | { 33 | "type": "image", 34 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip9.jpg", 35 | "size": "full", 36 | "aspectMode": "cover", 37 | "aspectRatio": "150:98", 38 | "gravity": "center" 39 | } 40 | ], 41 | "flex": 1 42 | } 43 | ] 44 | }, 45 | { 46 | "type": "box", 47 | "layout": "horizontal", 48 | "contents": [ 49 | { 50 | "type": "box", 51 | "layout": "vertical", 52 | "contents": [ 53 | { 54 | "type": "image", 55 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/flexsnapshot/clip/clip13.jpg", 56 | "aspectMode": "cover", 57 | "size": "full" 58 | } 59 | ], 60 | "cornerRadius": "100px", 61 | "width": "72px", 62 | "height": "72px" 63 | }, 64 | { 65 | "type": "box", 66 | "layout": "vertical", 67 | "contents": [ 68 | { 69 | "type": "text", 70 | "contents": [ 71 | { 72 | "type": "span", 73 | "text": "brown_05", 74 | "weight": "bold", 75 | "color": "#000000" 76 | }, 77 | { 78 | "type": "span", 79 | "text": " " 80 | }, 81 | { 82 | "type": "span", 83 | "text": "I went to the Brown&Cony cafe in Tokyo and took a picture" 84 | } 85 | ], 86 | "size": "sm", 87 | "wrap": true 88 | }, 89 | { 90 | "type": "box", 91 | "layout": "baseline", 92 | "contents": [ 93 | { 94 | "type": "text", 95 | "text": "1,140,753 Like", 96 | "size": "sm", 97 | "color": "#bcbcbc" 98 | } 99 | ], 100 | "spacing": "sm", 101 | "margin": "md" 102 | } 103 | ] 104 | } 105 | ], 106 | "spacing": "xl", 107 | "paddingAll": "20px" 108 | } 109 | ], 110 | "paddingAll": "0px" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /eg/flex-message-showcases/ticket.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bubble", 3 | "hero": { 4 | "type": "image", 5 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_3_movie.png", 6 | "size": "full", 7 | "aspectRatio": "20:13", 8 | "aspectMode": "cover", 9 | "action": { 10 | "type": "uri", 11 | "uri": "http://linecorp.com/" 12 | } 13 | }, 14 | "body": { 15 | "type": "box", 16 | "layout": "vertical", 17 | "spacing": "md", 18 | "contents": [ 19 | { 20 | "type": "text", 21 | "text": "BROWN'S ADVENTURE\nIN MOVIE", 22 | "wrap": true, 23 | "weight": "bold", 24 | "gravity": "center", 25 | "size": "xl" 26 | }, 27 | { 28 | "type": "box", 29 | "layout": "baseline", 30 | "margin": "md", 31 | "contents": [ 32 | { 33 | "type": "icon", 34 | "size": "sm", 35 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 36 | }, 37 | { 38 | "type": "icon", 39 | "size": "sm", 40 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 41 | }, 42 | { 43 | "type": "icon", 44 | "size": "sm", 45 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 46 | }, 47 | { 48 | "type": "icon", 49 | "size": "sm", 50 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png" 51 | }, 52 | { 53 | "type": "icon", 54 | "size": "sm", 55 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gray_star_28.png" 56 | }, 57 | { 58 | "type": "text", 59 | "text": "4.0", 60 | "size": "sm", 61 | "color": "#999999", 62 | "margin": "md", 63 | "flex": 0 64 | } 65 | ] 66 | }, 67 | { 68 | "type": "box", 69 | "layout": "vertical", 70 | "margin": "lg", 71 | "spacing": "sm", 72 | "contents": [ 73 | { 74 | "type": "box", 75 | "layout": "baseline", 76 | "spacing": "sm", 77 | "contents": [ 78 | { 79 | "type": "text", 80 | "text": "Date", 81 | "color": "#aaaaaa", 82 | "size": "sm", 83 | "flex": 1 84 | }, 85 | { 86 | "type": "text", 87 | "text": "Monday 25, 9:00PM", 88 | "wrap": true, 89 | "size": "sm", 90 | "color": "#666666", 91 | "flex": 4 92 | } 93 | ] 94 | }, 95 | { 96 | "type": "box", 97 | "layout": "baseline", 98 | "spacing": "sm", 99 | "contents": [ 100 | { 101 | "type": "text", 102 | "text": "Place", 103 | "color": "#aaaaaa", 104 | "size": "sm", 105 | "flex": 1 106 | }, 107 | { 108 | "type": "text", 109 | "text": "7 Floor, No.3", 110 | "wrap": true, 111 | "color": "#666666", 112 | "size": "sm", 113 | "flex": 4 114 | } 115 | ] 116 | }, 117 | { 118 | "type": "box", 119 | "layout": "baseline", 120 | "spacing": "sm", 121 | "contents": [ 122 | { 123 | "type": "text", 124 | "text": "Seats", 125 | "color": "#aaaaaa", 126 | "size": "sm", 127 | "flex": 1 128 | }, 129 | { 130 | "type": "text", 131 | "text": "C Row, 18 Seat", 132 | "wrap": true, 133 | "color": "#666666", 134 | "size": "sm", 135 | "flex": 4 136 | } 137 | ] 138 | } 139 | ] 140 | }, 141 | { 142 | "type": "box", 143 | "layout": "vertical", 144 | "margin": "xxl", 145 | "contents": [ 146 | { 147 | "type": "spacer" 148 | }, 149 | { 150 | "type": "image", 151 | "url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/linecorp_code_withborder.png", 152 | "aspectMode": "cover", 153 | "size": "xl" 154 | }, 155 | { 156 | "type": "text", 157 | "text": "You can enter the theater by using this code instead of a ticket", 158 | "color": "#aaaaaa", 159 | "wrap": true, 160 | "margin": "xxl", 161 | "size": "xs" 162 | } 163 | ] 164 | } 165 | ] 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /eg/get_number_of_messages.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use LINE::Bot::API; 7 | use Data::Dumper; 8 | use Time::Piece qw/localtime/; 9 | 10 | my $date = shift(@ARGV) // localtime->strftime('%Y%m%d'); 11 | 12 | my $bot = LINE::Bot::API->new( 13 | channel_secret => $ENV{CHANNEL_SECRET}, 14 | channel_access_token => $ENV{CHANNEL_ACCESS_TOKEN}, 15 | ); 16 | for my $method (qw(get_number_of_sent_reply_messages get_number_of_sent_push_messages get_number_of_sent_multicast_messages)) { 17 | print "-- $method\n"; 18 | print Dumper($bot->$method($date)); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /eg/line-bot-framework/bot-tmpl/help.txt: -------------------------------------------------------------------------------- 1 | line-bot-framework ([% app_name %]) 2 | 3 | commands 4 | 5 | memo stat memo mode 6 | help this content 7 | -------------------------------------------------------------------------------- /eg/line-bot-framework/bot-tmpl/memo.txt: -------------------------------------------------------------------------------- 1 | Welcome to memo mode! 2 | 3 | During 60 seconds, you can make a memo now. 4 | 5 | If you want this mode to stop, please send '@end' text message to me. 6 | -------------------------------------------------------------------------------- /eg/line-bot-framework/cpanfile: -------------------------------------------------------------------------------- 1 | requires 'LINE::Bot::API', '1.00'; 2 | requires 'Module::Load', '0.32'; 3 | requires 'Text::Xslate', '3.3.9'; 4 | -------------------------------------------------------------------------------- /eg/line-bot-framework/interactive-bot.psgi: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 'lib'; 4 | 5 | use Plack::Request; 6 | 7 | use LINE::Bot::API; 8 | use LINEBotFramework; 9 | 10 | my $channel_secret = $ENV{CHANNEL_SECRET}; 11 | my $channel_access_token = $ENV{CHANNEL_ACCESS_TOKEN}; 12 | my $messaging_api_endpoint = $ENV{MESSAGING_API_ENDPOINT}; 13 | my $callback_url = $ENV{CALLBACK_URL} // '/perl/callback'; 14 | 15 | my $bot = LINE::Bot::API->new( 16 | channel_secret => $channel_secret, 17 | channel_access_token => $channel_access_token, 18 | messaging_api_endpoint => $messaging_api_endpoint, 19 | ); 20 | 21 | my $framework = LINEBotFramework->new( 22 | base_class => 'ExampleBot', 23 | bot => $bot, 24 | xslate_config => { 25 | path => 'bot-tmpl', 26 | }, 27 | ); 28 | 29 | sub { 30 | my $env = shift; 31 | my $req = Plack::Request->new($env); 32 | 33 | unless ($req->method eq 'POST' && $req->path eq $callback_url) { 34 | return [200, [], ['Not Found']]; 35 | } 36 | 37 | unless ($framework->validate_signature($req->content, $req->header('X-Line-Signature'))) { 38 | return [200, [], ['bad request']]; 39 | } 40 | 41 | $framework->dispatcher($req->content); 42 | 43 | return [200, [], ["OK"]]; 44 | }; 45 | 46 | __END__ 47 | 48 | =head1 NAME 49 | 50 | interactive-bot.psgi - A example bot with a bot framework 51 | 52 | =head1 SYNOPSIS 53 | 54 | $ export CHANNEL_SECRET=YOUR CHANNEL SECRET 55 | $ export CHANNEL_ACCESS_TOKEN=YOUR CHANNEL ACCESS TOKEN 56 | $ plackup eg/interactive-bot.psgi 57 | 58 | =head1 COPYRIGHT & LICENSE 59 | 60 | Copyright 2016 LINE Corporation 61 | 62 | This Software Development Kit is licensed under The Artistic License 2.0. 63 | You may obtain a copy of the License at 64 | https://opensource.org/licenses/Artistic-2.0 65 | 66 | =cut 67 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/ExampleBot.pm: -------------------------------------------------------------------------------- 1 | package ExampleBot; 2 | use strict; 3 | use warnings; 4 | 5 | use JSON::XS; 6 | 7 | sub new { 8 | my($class, %args) = @_; 9 | my $self = bless \%args, $class; 10 | return $self; 11 | } 12 | 13 | # chatting context manager 14 | # you should use memcached, redis, rdbms for production 15 | 16 | my $CONTEXT = +{}; 17 | 18 | sub get_context { 19 | my($self, $source_type, $from_id) = @_; 20 | 21 | my $json = $CONTEXT->{"$source_type:$from_id"}; 22 | return ('default', +{}) unless $json; 23 | 24 | my $data = decode_json($json); 25 | return ($data->{context}, $data); 26 | } 27 | 28 | sub set_context { 29 | my($self, $source_type, $from_id, $context, $session) = @_; 30 | 31 | $session->{context} = $context; 32 | $CONTEXT->{"$source_type:$from_id"} = encode_json($session); 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/ExampleBot/Dispatcher.pm: -------------------------------------------------------------------------------- 1 | package ExampleBot::Dispatcher; 2 | use strict; 3 | use warnings; 4 | 5 | use LINEBotFramework::Dispatcher ':DSL'; 6 | 7 | sub dispatch { 8 | if (is_text) { 9 | if (context_is 'memo_mode') { 10 | if (session->{memo}{expire_at} < time()) { 11 | return +{ 12 | text => 'memo mode session is expired.', 13 | next_context => 'default', 14 | }; 15 | } 16 | 17 | if ($_ eq '@end') { 18 | return 'Memo#finish'; 19 | } else { 20 | return 'Memo#add'; 21 | } 22 | } 23 | 24 | if (/^(?:help|lost|forget)$/i) { 25 | return +{ 26 | page => 'help', 27 | template_vars => { app_name => 'example bot' }, 28 | }; 29 | } elsif (/memo/) { 30 | session->{memo} = +{ 31 | stack => [], 32 | expire_at => time() + 60, 33 | }; 34 | return +{ 35 | page => 'memo', 36 | next_context => 'memo_mode', 37 | }; 38 | } 39 | } 40 | } 41 | 1; 42 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/ExampleBot/Memo.pm: -------------------------------------------------------------------------------- 1 | package ExampleBot::Memo; 2 | use strict; 3 | use warnings; 4 | 5 | use LINEBotFramework::Response; 6 | 7 | sub add { 8 | my($class, $req) = @_; 9 | return unless $req->event->is_text_message; 10 | 11 | my $res = LINEBotFramework::Response->new; 12 | 13 | my $session = $req->session; 14 | $session->{memo}{stack} ||= []; 15 | push @{ $session->{memo}{stack} }, $req->event->text; 16 | 17 | $res->next_context($req->context); # next context is not changed 18 | 19 | return $res; 20 | } 21 | 22 | 23 | sub finish { 24 | my($class, $req) = @_; 25 | 26 | my $res = LINEBotFramework::Response->new; 27 | 28 | $res->send_text( 29 | text => 'The memos which you wrote are as follows.', 30 | ); 31 | 32 | for my $memo (@{ $req->session->{memo}{stack} }) { 33 | $res->send_text( 34 | text => $memo, 35 | ); 36 | } 37 | 38 | my $session = $req->session; 39 | $session->{memo}{stack} = []; 40 | 41 | $res->send_sticker( 42 | package_id => '1', 43 | sticker_id => int(rand(10))+1 + '', 44 | ); 45 | 46 | $res; 47 | } 48 | 49 | 1; 50 | 51 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/ExampleBot/Operation.pm: -------------------------------------------------------------------------------- 1 | package ExampleBot::Operation; 2 | use strict; 3 | use warnings; 4 | 5 | use LINEBotFramework::Response; 6 | 7 | sub on_follow { 8 | my($class, $req) = @_; 9 | 10 | my $res = LINEBotFramework::Response->new; 11 | 12 | $res->send_text( 13 | text => 'Welcome to line-bot-framework example bot!', 14 | ); 15 | 16 | $res->send_text( 17 | page => 'help', 18 | template_vars => { app_name => 'example bot' }, 19 | ); 20 | 21 | $res; 22 | } 23 | 24 | sub on_join { goto \&on_follow } 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/LINEBotFramework/Dispatcher.pm: -------------------------------------------------------------------------------- 1 | package LINEBotFramework::Dispatcher; 2 | use strict; 3 | use warnings; 4 | use parent 'Exporter'; 5 | 6 | our @EXPORT = qw/ 7 | new 8 | /; 9 | our @EXPORT_OK = qw/ 10 | context session event text 11 | is_text is_image is_video is_audio is_location is_sticker 12 | context_is 13 | /; 14 | our %EXPORT_TAGS = ( 15 | DSL => [ qw/ 16 | new 17 | context session event text 18 | is_text is_image is_video is_audio is_location is_sticker 19 | context_is 20 | / ], 21 | ); 22 | 23 | our $CONTEXT; 24 | our $SESSION; 25 | our $EVENT; 26 | our $TEXT; 27 | 28 | sub import { 29 | my($class, @args) = @_; 30 | 31 | my @export_args; 32 | for my $arg (@args) { 33 | if ($arg eq '-DSL') { 34 | push @export_args, ':DSL'; 35 | } else { 36 | push @export_args, $arg; 37 | } 38 | } 39 | $class->export_to_level(1, $class, @export_args); 40 | } 41 | 42 | sub new { 43 | my $class = shift; 44 | bless {}, $class; 45 | } 46 | 47 | sub context { $CONTEXT } 48 | sub session { $SESSION } 49 | sub event { $EVENT } 50 | sub text { $TEXT } 51 | 52 | # is 53 | sub is_text { $EVENT->is_text_message; } 54 | sub is_image { $EVENT->is_image_message; } 55 | sub is_video { $EVENT->is_video_message; } 56 | sub is_audio { $EVENT->is_audio_message; } 57 | sub is_location { $EVENT->is_location_message; } 58 | sub is_sticker { $EVENT->is_sticker_message; } 59 | 60 | # context 61 | sub context_is { 62 | my($context, $callback) = @_; 63 | $CONTEXT eq $context ? 1 : 0; 64 | } 65 | 66 | 1; 67 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/LINEBotFramework/Request.pm: -------------------------------------------------------------------------------- 1 | package LINEBotFramework::Request; 2 | use strict; 3 | use warnings; 4 | 5 | sub new { 6 | my($class, %args) = @_; 7 | bless { %args }, $class; 8 | } 9 | 10 | sub bot { $_[0]->{bot} } 11 | sub event { $_[0]->{event} } 12 | sub message { $_[0]->{message} } 13 | sub context { $_[0]->{context} } 14 | sub session { $_[0]->{session} } 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /eg/line-bot-framework/lib/LINEBotFramework/Response.pm: -------------------------------------------------------------------------------- 1 | package LINEBotFramework::Response; 2 | use strict; 3 | use warnings; 4 | 5 | use LINE::Bot::API::Builder::SendMessage; 6 | 7 | sub new { 8 | my($class, %args) = @_; 9 | bless { 10 | queues => [], 11 | next_context => 'default', 12 | }, $class; 13 | } 14 | 15 | sub next_context { 16 | my($self, $name) = @_; 17 | if (@_ == 2) { 18 | $self->{next_context} = $name; 19 | } else { 20 | $self->{next_context}; 21 | } 22 | } 23 | 24 | sub render_template { 25 | my($self, $path, $vars) = @_; 26 | $self->{xslate}->render($path, $vars); 27 | } 28 | 29 | sub finalize { 30 | my($self, %args) = @_; 31 | return 1 unless scalar(@{ $self->{queues} }); 32 | 33 | local $self->{bot} = $args{bot}; 34 | local $self->{xslate} = $args{xslate}; 35 | local $self->{base_obj} = $args{base_obj}; 36 | 37 | my $messages = LINE::Bot::API::Builder::SendMessage->new; 38 | 39 | for my $queue (@{ $self->{queues} }) { 40 | my $type = delete $queue->{type}; 41 | my $method = "exec_$type"; 42 | $self->$method($messages, %{ $queue }); 43 | } 44 | 45 | my $res = $self->{bot}->reply_message($args{reply_token}, $messages->build); 46 | 47 | # error handling 48 | unless ($res->is_success) { 49 | warn $res->message; 50 | for my $detail (@{ $res->details // []}) { 51 | if ($detail && ref($detail) eq 'HASH') { 52 | warn " detail: " . $detail->{message}; 53 | } 54 | } 55 | } 56 | 57 | return 1; 58 | } 59 | 60 | sub send_text { 61 | my($self, %args) = @_; 62 | $args{type} ||= 'text'; 63 | push @{ $self->{queues} }, \%args; 64 | } 65 | 66 | sub exec_text { 67 | my($self, $messages, %args) = @_; 68 | my $text; 69 | 70 | my $page = delete $args{page}; 71 | if (defined $page) { 72 | my $path = "$page.txt"; 73 | $text = $self->render_template($path, delete $args{template_vars}); 74 | } else { 75 | $text = $args{text}; 76 | } 77 | 78 | $messages->add_text( 79 | text => $text, 80 | ); 81 | } 82 | 83 | for my $type (qw/ image video audio sticker location imagemap template /) { 84 | my $add_method = "add_$type"; 85 | my $enqueue = sub { 86 | my($self, %args) = @_; 87 | $args{type} ||= $type; 88 | push @{ $self->{queues} }, \%args; 89 | }; 90 | my $exec = sub { 91 | my($self, $messages, %args) = @_; 92 | $messages->$add_method(%args); 93 | }; 94 | 95 | no strict 'refs'; 96 | *{"LINEBotFramework::Response::send_$type"} = $enqueue; 97 | *{"LINEBotFramework::Response::exec_$type"} = $exec; 98 | } 99 | 100 | 1; 101 | -------------------------------------------------------------------------------- /eg/push-flex-message-showcases.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use FindBin '$Bin'; 5 | use File::Glob ':bsd_glob'; 6 | use JSON; 7 | use Data::Dumper qw(Dumper); 8 | 9 | use LINE::Bot::API; 10 | 11 | my $channel_secret = $ENV{CHANNEL_SECRET} or die "requiers env: CHANNEL_SECRET"; 12 | my $channel_access_token = $ENV{CHANNEL_ACCESS_TOKEN} or die "requires env: CHANNEL_ACCESS_TOKEN"; 13 | my $messaging_api_endpoint = $ENV{MESSAGING_API_ENDPOINT}; 14 | 15 | my($to_id) = @ARGV; 16 | 17 | $to_id or die "requires \$ARGV[0]: a user ID"; 18 | 19 | my $bot = LINE::Bot::API->new( 20 | channel_secret => $channel_secret, 21 | channel_access_token => $channel_access_token, 22 | $messaging_api_endpoint ? ( 23 | messaging_api_endpoint => $messaging_api_endpoint, 24 | ):(), 25 | ); 26 | 27 | my $json = JSON->new->utf8; 28 | 29 | for my $file (bsd_glob($Bin . '/flex-message-showcases/*.json')) { 30 | 31 | my ($fh, $flex_message); 32 | print "# $file\n"; 33 | open($fh, '<', $file); 34 | $flex_message = do { local $/; $json->decode(scalar <$fh>) }; 35 | close($fh); 36 | 37 | my $builder = LINE::Bot::API::Builder::SendMessage->new(); 38 | $builder->add_text( text => "Showcase: " . ($file =~ s{^.+\/}{}r) ); 39 | $builder->add($flex_message); 40 | 41 | my $messages = $builder->build; 42 | 43 | print $json->encode($messages); 44 | 45 | my $res = $bot->push_message($to_id, $messages); 46 | unless ($res->is_success) { 47 | print Dumper([ res => $res ]); 48 | } 49 | } 50 | 51 | 52 | __END__ 53 | 54 | =head1 NAME 55 | 56 | push-flex-message-showcases.pl - example script for push a Flex Message 57 | 58 | =head1 SYNOPSIS 59 | 60 | $ export CHANNEL_SECRET=YOUR CHANNEL SECRET 61 | $ export CHANNEL_ACCESS_TOKEN=YOUR CHANNEL ACCESS TOKEN 62 | $ perl push-flex-message-showcases.pl 63 | 64 | =head1 References: 65 | 66 | Flex Message: L 67 | 68 | =head1 COPYRIGHT & LICENSE 69 | 70 | Copyright 2016 LINE Corporation 71 | 72 | This Software Development Kit is licensed under The Artistic License 2.0. 73 | You may obtain a copy of the License at 74 | https://opensource.org/licenses/Artistic-2.0 75 | 76 | =cut 77 | -------------------------------------------------------------------------------- /eg/push_imagemap-template.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use lib 'lib'; 4 | 5 | use LINE::Bot::API; 6 | 7 | use LINE::Bot::API; 8 | use LINE::Bot::API::Builder::SendMessage; 9 | use LINE::Bot::API::Builder::ImagemapMessage; 10 | use LINE::Bot::API::Builder::TemplateMessage; 11 | 12 | my $channel_secret = $ENV{CHANNEL_SECRET}; 13 | my $channel_access_token = $ENV{CHANNEL_ACCESS_TOKEN}; 14 | my $messaging_api_endpoint = $ENV{MESSAGING_API_ENDPOINT}; 15 | 16 | my $imagemap_image_url = $ENV{IMAGEMAP_IMAGE_URL}; 17 | my $template_image_url = $ENV{TEMPLATE_IMAGE_URL}; 18 | 19 | my($to_id, $text) = @ARGV; 20 | 21 | my $bot = LINE::Bot::API->new( 22 | channel_secret => $channel_secret, 23 | channel_access_token => $channel_access_token, 24 | messaging_api_endpoint => $messaging_api_endpoint, 25 | ); 26 | 27 | my $messages = LINE::Bot::API::Builder::SendMessage->new->add_text( text => $text ); 28 | 29 | # Imagemap Message 30 | my $imagemap = LINE::Bot::API::Builder::ImagemapMessage->new( 31 | base_url => $imagemap_image_url, 32 | alt_text => 'altText', 33 | base_width => 1040, 34 | base_height => 1040, 35 | )->add_uri_action( 36 | uri => 'http://example.com/', 37 | area_x => 0, 38 | area_y => 0, 39 | area_width => 1040, 40 | area_height => 520, 41 | )->add_message_action( 42 | text => 'message', 43 | area_x => 0, 44 | area_y => 520, 45 | area_width => 1040, 46 | area_height => 520, 47 | ); 48 | $messages->add_imagemap($imagemap->build); 49 | 50 | 51 | # Template Message 52 | my $buttons = LINE::Bot::API::Builder::TemplateMessage->new_buttons( 53 | alt_text => 'altText', 54 | image_url => $template_image_url, 55 | title => 'buttons', 56 | text => 'description', 57 | )->add_postback_action( 58 | label => 'postback', 59 | data => 'postback data', 60 | text => 'postback message', 61 | )->add_message_action( 62 | label => 'message', 63 | text => 'message', 64 | )->add_uri_action( 65 | label => 'uri', 66 | uri => 'http://example.com/', 67 | )->add_message_action( 68 | label => 'message2', 69 | text => 'message2', 70 | ); 71 | $messages->add_template($buttons->build); 72 | 73 | my $confirm = LINE::Bot::API::Builder::TemplateMessage->new_confirm( 74 | alt_text => 'altText', 75 | text => 'confirm', 76 | )->add_postback_action( 77 | label => 'postback', 78 | data => 'postback data', 79 | text => 'postback message', 80 | )->add_message_action( 81 | label => 'message', 82 | text => 'message', 83 | )->add_uri_action( 84 | label => 'uri', 85 | uri => 'http://example.com/', 86 | ); 87 | $messages->add_template($confirm->build); 88 | 89 | my $carousel = LINE::Bot::API::Builder::TemplateMessage->new_carousel( 90 | alt_text => 'altText', 91 | ); 92 | for my $i (1..5) { 93 | my $column = LINE::Bot::API::Builder::TemplateMessage::Column->new( 94 | image_url => $template_image_url, 95 | title => "carousel $i", 96 | text => "description $i", 97 | )->add_postback_action( 98 | label => 'postback', 99 | data => 'postback data', 100 | text => 'postback message', 101 | )->add_message_action( 102 | label => 'message', 103 | text => 'message', 104 | )->add_uri_action( 105 | label => 'uri', 106 | uri => 'http://example.com/', 107 | ); 108 | $carousel->add_column($column->build); 109 | } 110 | $messages->add_template($carousel->build); 111 | 112 | $bot->push_message($to_id, $messages->build); 113 | 114 | 115 | __END__ 116 | 117 | =head1 NAME 118 | 119 | push_imagemap-template.pl - example script for push message (imagemap/template) 120 | 121 | =head1 SYNOPSIS 122 | 123 | $ export CHANNEL_SECRET=YOUR CHANNEL SECRET 124 | $ export CHANNEL_ACCESS_TOKEN=YOUR CHANNEL ACCESS TOKEN 125 | $ export IMAGEMAP_IMAGE_URL=https://example.com/images/cats 126 | $ export TEMPLATE_IMAGE_URL=https://example.com/images/template_image.jpg 127 | $ perl push_imagemap-template.pl 128 | 129 | =head1 COPYRIGHT & LICENSE 130 | 131 | Copyright 2016 LINE Corporation 132 | 133 | This Software Development Kit is licensed under The Artistic License 2.0. 134 | You may obtain a copy of the License at 135 | https://opensource.org/licenses/Artistic-2.0 136 | 137 | =cut 138 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Builder/FlexMessage.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Builder::FlexMessage; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | sub new { 7 | my ($class, %args) = @_; 8 | die "`json` attribute is required" unless defined $args{json}; 9 | return bless { _json => $args{json} }, $class; 10 | } 11 | 12 | sub build { 13 | my ($self) = @_; 14 | return $self->{_build} //= decode_json($self->{_json}); 15 | } 16 | 17 | 1; 18 | 19 | __END__ 20 | 21 | =head1 NAME 22 | 23 | LINE::Bot::API::Builder::FlexMessage 24 | 25 | =head1 SYNOPSIS 26 | 27 | my $message = LINE::Bot::API::Builder::FlexMessage->new( json => $json_text ); 28 | 29 | $bot->push_message( $user_id, $message->build ); 30 | 31 | =head1 DESCRIPTION 32 | 33 | This module can be used to convert the output of L to an object. 34 | 35 | Structurally, a flex message is represented as an object in JSON with 36 | smaller components. Here's an minimal example: 37 | 38 | { 39 | "type": "flex", 40 | "altText": "This is a Flex Message", 41 | "contents": { 42 | "type": "bubble", 43 | "body": { 44 | "type": "box", 45 | "layout": "horizontal", 46 | "contents": [ 47 | { 48 | "type": "text", 49 | "text": "Hello," 50 | }, 51 | { 52 | "type": "text", 53 | "text": "World!" 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | 60 | This module merely parse the given json text provided by the C 61 | attribute and directly use the structure as the message content. 62 | 63 | It is created to let developers able to basically paste the JSON text 64 | produced by Flex Message Simular into code. 65 | 66 | =head1 METHODS 67 | 68 | =over 4 69 | 70 | =item new( json => $json_text ) 71 | 72 | Object constructure. The "json" parameter is required. The value 73 | should be a normal scalar variable containing json text. 74 | 75 | =item build() 76 | 77 | Returns the message object that can be pass thru push_message method. 78 | 79 | =back 80 | 81 | =head1 SEE ALSO 82 | 83 | L 84 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Builder/ImagemapMessage.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Builder::ImagemapMessage; 2 | use strict; 3 | use warnings; 4 | 5 | sub new { 6 | my($class, %args) = @_; 7 | my %o = ( 8 | type => 'imagemap', 9 | baseUrl => $args{base_url}, 10 | altText => $args{alt_text}, 11 | baseSize => +{ 12 | width => $args{base_width}, 13 | height => $args{base_height}, 14 | }, 15 | actions => $args{actions} // [], 16 | ); 17 | 18 | if ($args{video}) { 19 | $o{video} = {}; 20 | for my $attr (qw(originalContentUrl previewImageUrl)) { 21 | $o{video}{$attr} = $args{video}{$attr}; 22 | } 23 | for my $attr (qw(x y width height)) { 24 | $o{video}{area}{$attr} = $args{video}{area}{$attr}; 25 | } 26 | 27 | if ($args{video}{externalLink}) { 28 | for my $attr (qw(label linkUri)) { 29 | $o{video}{externalLink}{$attr} = $args{video}{externalLink}{$attr}; 30 | } 31 | } 32 | } 33 | 34 | return bless \%o, $class; 35 | } 36 | 37 | sub build { 38 | my($self, ) = @_; 39 | +{ %{ $self } }; 40 | } 41 | 42 | sub add_action { 43 | my($self, $action) = @_; 44 | push @{ $self->{actions} }, $action; 45 | $self; 46 | } 47 | 48 | sub add_uri_action { 49 | my($self, %args) = @_; 50 | $self->add_action(+{ 51 | type => 'uri', 52 | linkUri => $args{uri}, 53 | area => +{ 54 | x => $args{area_x}, 55 | y => $args{area_y}, 56 | width => $args{area_width}, 57 | height => $args{area_height}, 58 | }, 59 | }); 60 | } 61 | 62 | sub add_message_action { 63 | my($self, %args) = @_; 64 | $self->add_action(+{ 65 | type => 'message', 66 | text => $args{text}, 67 | area => +{ 68 | x => $args{area_x}, 69 | y => $args{area_y}, 70 | width => $args{area_width}, 71 | height => $args{area_height}, 72 | }, 73 | }); 74 | } 75 | 76 | 1; 77 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Builder/SendMessage.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Builder::SendMessage; 2 | use strict; 3 | use warnings; 4 | 5 | sub new { 6 | my($class, ) = @_; 7 | bless [], $class; 8 | } 9 | 10 | sub build { 11 | my($self, ) = @_; 12 | +[ @$self ]; 13 | } 14 | 15 | sub add { 16 | my($self, $message) = @_; 17 | push @{ $self }, $message; 18 | $self; 19 | } 20 | 21 | sub add_text { 22 | my($self, %args) = @_; 23 | $self->add(+{ 24 | type => 'text', 25 | text => $args{text}, 26 | $args{sender} ? ( 27 | sender => $args{sender}, 28 | ) : (), 29 | $args{emojis} ? ( 30 | emojis => $args{emojis}, 31 | ):(), 32 | }); 33 | 34 | $self; 35 | } 36 | 37 | sub add_image { 38 | my($self, %args) = @_; 39 | $self->add(+{ 40 | $args{sender} ? ( 41 | sender => $args{sender}, 42 | ) : (), 43 | type => 'image', 44 | originalContentUrl => $args{image_url}, 45 | previewImageUrl => $args{preview_url}, 46 | }); 47 | 48 | $self; 49 | } 50 | 51 | sub add_video { 52 | my($self, %args) = @_; 53 | $self->add(+{ 54 | $args{sender} ? ( 55 | sender => $args{sender}, 56 | ) : (), 57 | type => 'video', 58 | originalContentUrl => $args{video_url}, 59 | previewImageUrl => $args{preview_url}, 60 | }); 61 | 62 | $self; 63 | } 64 | 65 | sub add_audio { 66 | my($self, %args) = @_; 67 | $self->add(+{ 68 | $args{sender} ? ( 69 | sender => $args{sender}, 70 | ) : (), 71 | type => 'audio', 72 | originalContentUrl => $args{audio_url}, 73 | duration => $args{duration}, 74 | }); 75 | 76 | $self; 77 | } 78 | 79 | sub add_location { 80 | my($self, %args) = @_; 81 | $self->add(+{ 82 | $args{sender} ? ( 83 | sender => $args{sender}, 84 | ) : (), 85 | type => 'location', 86 | title => $args{title}, 87 | address => $args{address}, 88 | latitude => $args{latitude}, 89 | longitude => $args{longitude}, 90 | }); 91 | 92 | $self; 93 | } 94 | 95 | sub add_sticker { 96 | my($self, %args) = @_; 97 | $self->add(+{ 98 | $args{sender} ? ( 99 | sender => $args{sender}, 100 | ) : (), 101 | type => 'sticker', 102 | packageId => $args{package_id}, 103 | stickerId => $args{sticker_id}, 104 | }); 105 | 106 | $self; 107 | } 108 | 109 | # If you want this method to use, I recommend using LINE::Bot::API::Builder::ImagemapMessage class for you. 110 | sub add_imagemap { 111 | my($self, $imagemap) = @_; 112 | $self->add($imagemap); 113 | } 114 | 115 | # If you want this method to use, I recommend using LINE::Bot::API::Builder::TemplateMessage class for you. 116 | sub add_template { 117 | my($self, $template) = @_; 118 | $self->add($template); 119 | } 120 | 121 | 1; 122 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Client.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Client; 2 | use strict; 3 | use warnings; 4 | 5 | use Carp 'croak'; 6 | use File::Basename 'basename'; 7 | 8 | sub new { 9 | my(undef, %args) = @_; 10 | my $backend = 'Furl'; 11 | if ($args{http_client} && $args{http_client}{backend}) { 12 | $backend = $args{http_client}{backend}; 13 | } 14 | my $klass = join '::', __PACKAGE__, $backend; 15 | eval "use $klass;"; ## no critic 16 | croak $@ if $@; 17 | $klass->new(%args); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/AccountLink.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::AccountLink; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_account_link_event { 1 } 7 | 8 | sub source { $_[0]{source} } 9 | 10 | sub replyToken { $_[0]{replyToken} } 11 | 12 | sub link { $_[0]{link} } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Base.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Base; 2 | use strict; 3 | use warnings; 4 | 5 | use Carp 'croak'; 6 | 7 | sub new { 8 | my($class, %args) = @_; 9 | bless { %args }, $class; 10 | } 11 | 12 | # Accessors for Event-level properties. https://developers.line.biz/en/reference/messaging-api/#common-properties 13 | sub type { $_[0]->{type} } 14 | sub mode { $_[0]->{mode} } 15 | sub timestamp { $_[0]->{timestamp} } 16 | sub webhook_event_id { $_[0]->{webhookEventId} } 17 | 18 | # Whether the webhook event is a redelivered one or not. 19 | # https://developers.line.biz/en/docs/messaging-api/receiving-messages/#redelivered-webhooks 20 | sub is_redelivery { $_[0]->{deliveryContext}{isRedelivery} } 21 | 22 | # Unfollow and Leave events don't have this 23 | sub reply_token { $_[0]->{replyToken} } 24 | 25 | # type 26 | sub is_message_event { 0 } 27 | sub is_follow_event { 0 } 28 | sub is_unfollow_event { 0 } 29 | sub is_join_event { 0 } 30 | sub is_leave_event { 0 } 31 | sub is_member_join_event { 0 } 32 | sub is_member_leave_event { 0 } 33 | sub is_postback_event { 0 } 34 | sub is_beacon_detection_event { 0 } 35 | sub is_device_link_event { 0 } 36 | sub is_device_unlink_event { 0 } 37 | sub is_account_link_event { 0 } 38 | sub is_unsend_event { 0 } 39 | sub is_video_viewing_complete_event { 0 } 40 | sub is_unknown_event { 0 } 41 | 42 | # source field 43 | sub is_user_event { $_[0]->{source}{type} eq 'user' } 44 | sub is_group_event { $_[0]->{source}{type} eq 'group' } 45 | sub is_room_event { $_[0]->{source}{type} eq 'room' } 46 | 47 | sub user_id { $_[0]->{source}{userId} } 48 | 49 | sub group_id { 50 | my $self = shift; 51 | croak 'This event source is not a group type.' unless $self->is_group_event; 52 | $self->{source}{groupId}; 53 | } 54 | 55 | sub room_id { 56 | my $self = shift; 57 | croak 'This event source is not a room type.' unless $self->is_room_event; 58 | $self->{source}{roomId}; 59 | } 60 | 61 | 1; 62 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/BeaconDetection.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::BeaconDetection; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_beacon_detection_event { 1 } 7 | 8 | sub beacon_hwid { $_[0]->{beacon}{hwid} } 9 | sub beacon_type { $_[0]->{beacon}{type} } 10 | sub beacon_device_message { pack 'H*', $_[0]->{beacon}{dm} } 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Follow.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Follow; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_follow_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Join.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Join; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_join_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Leave.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Leave; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_leave_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/MemberJoin.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::MemberJoin; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_member_join_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/MemberLeave.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::MemberLeave; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_member_leave_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Message.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Message; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | use Carp 'carp'; 7 | our @CARP_NOT = qw( LINE::Bot::API::Event::Message LINE::Bot::API::Event LINE::Bot::API); 8 | 9 | my %TYPE2CLASS = ( 10 | text => 'LINE::Bot::API::Event::Message::Text', 11 | image => 'LINE::Bot::API::Event::Message::Image', 12 | video => 'LINE::Bot::API::Event::Message::Video', 13 | audio => 'LINE::Bot::API::Event::Message::Audio', 14 | location => 'LINE::Bot::API::Event::Message::Location', 15 | sticker => 'LINE::Bot::API::Event::Message::Sticker', 16 | file => 'LINE::Bot::API::Event::Message::File', 17 | ); 18 | 19 | sub new { 20 | my($class, %args) = @_; 21 | 22 | my $type = $args{message}{type}; 23 | my $message_class = $TYPE2CLASS{$type}; 24 | unless ($message_class) { 25 | carp 'Unsupported message type: ' . $type; 26 | $message_class = $class; 27 | } 28 | 29 | bless { %args }, $message_class; 30 | } 31 | 32 | sub is_message_event { 1 } 33 | 34 | sub message_id { $_[0]->{message}{id} } 35 | 36 | sub message_type { $_[0]->{message}{type} } 37 | 38 | sub is_text_message { 0 } 39 | sub is_image_message { 0 } 40 | sub is_video_message { 0 } 41 | sub is_audio_message { 0 } 42 | sub is_location_message { 0 } 43 | sub is_sticker_message { 0 } 44 | sub is_file_message { 0 } 45 | 46 | package LINE::Bot::API::Event::Message::Text { 47 | use parent 'LINE::Bot::API::Event::Message'; 48 | 49 | sub is_text_message { 1 } 50 | 51 | sub text { $_[0]->{message}{text} } 52 | sub emojis { $_[0]->{message}{emojis} } 53 | sub mention { $_[0]->{mention} } 54 | } 55 | 56 | package LINE::Bot::API::Event::Message::Image { 57 | use parent 'LINE::Bot::API::Event::Message'; 58 | 59 | sub is_image_message { 1 } 60 | 61 | sub content_provider { $_[0]->{message}{contentProvider} } 62 | 63 | sub image_set { $_[0]->{message}{imageSet} } 64 | } 65 | 66 | package LINE::Bot::API::Event::Message::Video { 67 | use parent 'LINE::Bot::API::Event::Message'; 68 | 69 | sub is_video_message { 1 } 70 | 71 | sub content_provider { $_[0]->{message}{contentProvider} } 72 | } 73 | 74 | package LINE::Bot::API::Event::Message::Audio { 75 | use parent 'LINE::Bot::API::Event::Message'; 76 | 77 | sub is_audio_message { 1 } 78 | 79 | sub content_provider { $_[0]->{message}{contentProvider} } 80 | } 81 | 82 | package LINE::Bot::API::Event::Message::Location { 83 | use parent 'LINE::Bot::API::Event::Message'; 84 | 85 | sub is_location_message { 1 } 86 | 87 | sub title { $_[0]->{message}{title} } 88 | sub address { $_[0]->{message}{address} } 89 | sub latitude { $_[0]->{message}{latitude} } 90 | sub longitude { $_[0]->{message}{longitude} } 91 | } 92 | 93 | package LINE::Bot::API::Event::Message::Sticker { 94 | use parent 'LINE::Bot::API::Event::Message'; 95 | 96 | sub is_sticker_message { 1 } 97 | 98 | sub package_id { $_[0]->{message}{packageId} } 99 | sub sticker_id { $_[0]->{message}{stickerId} } 100 | sub stickerResourceType { $_[0]->{message}{stickerResourceType} } 101 | sub keywords { $_[0]->{message}{keywords} } 102 | } 103 | 104 | package LINE::Bot::API::Event::Message::File { 105 | use parent 'LINE::Bot::API::Event::Message'; 106 | 107 | sub is_file_message { 1 } 108 | 109 | sub file_name { $_[0]->{message}{fileName} } 110 | sub file_size { $_[0]->{message}{fileSize} } 111 | } 112 | 113 | 1; 114 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Postback.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Postback; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_postback_event { 1 } 7 | 8 | sub postback_data { $_[0]->{postback}{data} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Things.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Things; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | use Carp 'carp'; 7 | our @CARP_NOT = qw( LINE::Bot::API::Event::Things LINE::Bot::API::Event LINE::Bot::API); 8 | 9 | my %TYPE2CLASS = ( 10 | link => 'LINE::Bot::API::Event::Things::Link', 11 | unlink => 'LINE::Bot::API::Event::Things::Unlink', 12 | ); 13 | 14 | sub new { 15 | my($class, %args) = @_; 16 | 17 | my $type = $args{things}{type}; 18 | my $things_class = $TYPE2CLASS{$type}; 19 | unless ($things_class) { 20 | carp 'Unsupported Things type: ' . $type; 21 | $things_class = $class; 22 | } 23 | 24 | bless { %args }, $things_class; 25 | } 26 | 27 | sub is_things_event { 1 } 28 | 29 | sub things_device_id { $_[0]->{things}{deviceId} } 30 | sub things_type { $_[0]->{things}{type} } 31 | 32 | sub is_device_link { 0 } 33 | sub is_device_unlink { 0 } 34 | 35 | package LINE::Bot::API::Event::Things::Link { 36 | use parent 'LINE::Bot::API::Event::Things'; 37 | 38 | sub is_device_link { 1 } 39 | } 40 | 41 | package LINE::Bot::API::Event::Things::Unlink { 42 | use parent 'LINE::Bot::API::Event::Things'; 43 | 44 | sub is_device_unlink { 1 } 45 | } 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Unfollow.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Unfollow; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_unfollow_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Unknown.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Unknown; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_unknown_event { 1 } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/Unsend.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::Unsend; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_unsend_event { 1 } 7 | 8 | sub message_id { $_[0]->{unsend}{messageId} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Event/VideoViewingComplete.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Event::VideoViewingComplete; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Event::Base'; 5 | 6 | sub is_video_viewing_complete_event { 1 } 7 | 8 | sub tracking_id { $_[0]->{videoPlayComplete}{trackingId} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceAuthorityLevel.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceAuthorityLevel; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceAuthorityLevel 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Get the authority level of the audience" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub authorityLevel { $_[0]->{authorityLevel} } 18 | 19 | # Aliases 20 | sub authority_level { $_[0]->{authorityLevel} } 21 | 22 | 1; 23 | 24 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceData.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceData; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceData 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Get audience data" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub audienceGroupId { $_[0]->{audienceGroupId} } 18 | sub type { $_[0]->{type} } 19 | sub description { $_[0]->{description} } 20 | sub status { $_[0]->{status} } 21 | sub failedType { $_[0]->{failedType} } 22 | sub audienceCount { $_[0]->{audienceCount} } 23 | sub created { $_[0]->{created} } 24 | sub requestId { $_[0]->{requestId} } 25 | sub clickUrl { $_[0]->{clickUrl} } 26 | sub isIfaAudience { $_[0]->{isIfaAudience} } 27 | sub permission { $_[0]->{permission} } 28 | sub createRoute { $_[0]->{createRoute} } 29 | sub jobs { $_[0]->{jobs} } 30 | 31 | # Aliases 32 | sub audience_group_id { $_[0]->{audienceGroupId} } 33 | sub failed_type { $_[0]->{failedType} } 34 | sub audience_count { $_[0]->{audienceCount} } 35 | sub request_id { $_[0]->{requestId} } 36 | sub click_url { $_[0]->{clickUrl} } 37 | sub is_ifa_audience { $_[0]->{isIfaAudience} } 38 | sub create_route { $_[0]->{createRoute} } 39 | 40 | 1; 41 | 42 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceGroupForClickRetargeting.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceGroupForClickRetargeting; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceGroupForClickRetargeting 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Create audience for click-based retargeting" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | 18 | sub audienceGroupId { $_[0]->{audienceGroupId} } 19 | sub type { $_[0]->{type} } 20 | sub description { $_[0]->{description} } 21 | sub created { $_[0]->{created} } 22 | sub requestId { $_[0]->{requestId} } 23 | sub clickUrl { $_[0]->{clickUrl} } 24 | 25 | 26 | #Aliases 27 | sub audience_group_id { $_[0]->{audienceGroupId} } 28 | sub request_id { $_[0]->{requestId} } 29 | sub click_url { $_[0]->{clickUrl} } 30 | 31 | 1; 32 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceGroupForImpressionRetargeting.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceGroupForImpressionRetargeting; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceGroupForImpressionRetargeting 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Create audience for impression-based retargeting" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub audienceGroupId { $_[0]->{audienceGroupId} } 18 | sub type { $_[0]->{type} } 19 | sub description { $_[0]->{description} } 20 | sub created { $_[0]->{created} } 21 | sub requestId { $_[0]->{requestId} } 22 | 23 | #Aliases 24 | sub audience_group_id { $_[0]->{audienceGroupId} } 25 | sub request_id { $_[0]->{requestId} } 26 | 27 | 1; 28 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceGroupForUploadingUserId.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceGroupForUploadingUserId; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceGroupForUploadingUserId 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Create audiece for uploading user IDs" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub audienceGroupId { $_[0]->{audienceGroupId} } 18 | sub type { $_[0]->{type} } 19 | sub description { $_[0]->{description} } 20 | sub created { $_[0]->{created} } 21 | 22 | # Aliases 23 | sub audience_group_id { $_[0]->{audienceGroupId} } 24 | 25 | 1; 26 | 27 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/AudienceMultipleData.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::AudienceMultipleData; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::AudienceMultipleData 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Get data for multiple audiences" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub audienceGroups { $_[0]->{audienceGroups} } 18 | sub hasNextPage { $_[0]->{hasNextPage} } 19 | sub totalCount { $_[0]->{totalCount} } 20 | sub readWriteAudienceGroupTotalCount { $_[0]->{readWriteAudienceGroupTotalCount} } 21 | sub page { $_[0]->{page} } 22 | sub size { $_[0]->{size} } 23 | 24 | # Aliases 25 | sub audience_groups { $_[0]->{audienceGroups} } 26 | sub has_next_page { $_[0]->{hasNextPage} } 27 | sub total_count { $_[0]->{totalCount} } 28 | sub read_write_audience_group_total_count { $_[0]->{readWriteAudienceGroupTotalCount} } 29 | 30 | 1; 31 | 32 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/BotInfo.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::BotInfo; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub userId { $_[0]->{userId} } 7 | sub basicId { $_[0]->{basicId} } 8 | sub premiumId { $_[0]->{premiumId} } 9 | sub displayName { $_[0]->{displayName} } 10 | sub pictureUrl { $_[0]->{pictureUrl} } 11 | sub chatMode { $_[0]->{chatMode} } 12 | sub markAsReadMode { $_[0]->{markAsReadMode} } 13 | 14 | 1; 15 | 16 | __END__ 17 | 18 | =head1 NAME 19 | 20 | LINE::Bot::API::Response::BotInfo 21 | 22 | =head1 DESCRIPTION 23 | 24 | This cllass corresponds to the response object of "Bot Info" API as described in this page: L 25 | 26 | For each top-level properties, there is a corresponding method with the same name which provides access to the value of the property. 27 | 28 | =head1 METHODS 29 | 30 | =over 4 31 | 32 | =item userId 33 | 34 | =item basicId 35 | 36 | =item premiumId 37 | 38 | =item displayName 39 | 40 | =item pictureUrl 41 | 42 | =item chatMode 43 | 44 | =item markAsReadMode 45 | 46 | =back 47 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Common.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Common; 2 | use strict; 3 | use warnings; 4 | 5 | use LINE::Bot::API::Response::Error; 6 | 7 | sub new { 8 | my($class, %args) = @_; 9 | my $self = bless { %args }, $class; 10 | bless $self, 'LINE::Bot::API::Response::Error' unless $self->is_success; 11 | $self; 12 | } 13 | 14 | sub is_success { $_[0]->{http_status} == 200 } 15 | sub http_status { $_[0]->{http_status} } 16 | sub x_line_request_id { $_[0]->{http_headers}->header('X-Line-Request-Id') } 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Content.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Content; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub fh { $_[0]->{fh} } 7 | sub headers { $_[0]->{headers} } 8 | 9 | 1; 10 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Count.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Count; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | LINE::Bot::API::Response::Count 8 | 9 | =head1 DESCRIPTION 10 | 11 | This class correspond to below. 12 | - "Get members in group count" : See also L 13 | - "Get members in room count" : See also L 14 | 15 | =cut 16 | 17 | sub count { $_[0]->{count} } 18 | 19 | 1; 20 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Error.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Error; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub message { $_[0]->{message} } 7 | sub details { $_[0]->{details} } 8 | 9 | sub error { $_[0]->{error} } 10 | sub error_description { $_[0]->{error_description} } 11 | 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Followers.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Followers; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent 'LINE::Bot::API::Response::Common'; 6 | 7 | sub user_ids { $_[0]->{userIds} } 8 | sub next { $_[0]->{next} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/FriendDemographics.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::FriendDemographics; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub available { $_[0]->{available} } 7 | sub genders { $_[0]->{genders} } 8 | sub ages { $_[0]->{ages} } 9 | sub areas { $_[0]->{areas} } 10 | sub appTypes { $_[0]->{appTypes} } 11 | sub subscriptionPeriods { $_[0]->{subscriptionPeriods} } 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/GroupMemberProfile.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::GroupMemberProfile; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub display_name { $_[0]->{displayName} } 7 | sub user_id { $_[0]->{userId} } 8 | sub picture_url { $_[0]->{pictureUrl} } 9 | 10 | 1; 11 | 12 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/GroupSummary.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::GroupSummary; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | =head1 NAME 7 | 8 | LINE::Bot::API::Response::GroupSummary 9 | 10 | =head1 DESCRIPTION 11 | 12 | This class correspond to the "Get group summary" response as described in 13 | this page : L 14 | 15 | =cut 16 | 17 | sub groupId { $_[0]->{groupId} } 18 | sub groupName { $_[0]->{groupName} } 19 | sub pictureUrl { $_[0]->{pictureUrl} } 20 | 21 | # Aliases 22 | sub group_id { $_[0]->{groupId} } 23 | sub group_name { $_[0]->{groupName} } 24 | sub picture_url { $_[0]->{pictureUrl} } 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/IssueLinkToken.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::IssueLinkToken; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent 'LINE::Bot::API::Response::Common'; 6 | 7 | sub link_token { $_[0]->{linkToken} } 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/NarrowcastStatus.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::NarrowcastStatus; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | 7 | =head1 NAME 8 | 9 | LINE::Bot::API::Response::NarrowcastStatus 10 | 11 | =head1 DESCRIPTION 12 | 13 | This class correspond to the "Get narrowcast message status" response as described in 14 | this page : L 15 | 16 | =cut 17 | 18 | sub phase { $_[0]->{phase} } 19 | sub successCount { $_[0]->{successCount} } 20 | sub failureCount { $_[0]->{failureCount} } 21 | sub targetCount { $_[0]->{targetCount} } 22 | sub failedDescription { $_[0]->{failedDescription} } 23 | sub errorCode { $_[0]->{errorCode} } 24 | sub acceptedTime { $_[0]->{acceptedTime} } 25 | sub completedTime { $_[0]->{completedTime} } 26 | 27 | # Aliases 28 | sub success_count { $_[0]->{successCount} } 29 | sub failure_count { $_[0]->{failureCount} } 30 | sub target_count { $_[0]->{targetCount} } 31 | sub failed_description { $_[0]->{failedDescription} } 32 | sub error_code { $_[0]->{errorCode} } 33 | sub accepted_time { $_[0]->{acceptedTime} } 34 | sub completed_time { $_[0]->{completedTime} } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/NumberOfFollowers.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::NumberOfFollowers; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub status { $_[0]->{status} } 7 | sub followers { $_[0]->{followers} } 8 | sub targetedReaches { $_[0]->{targetedReaches} } 9 | sub blocks { $_[0]->{blocks} } 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/NumberOfMessageDeliveries.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::NumberOfMessageDeliveries; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub status { $_[0]->{status} } 7 | sub broadcast { $_[0]->{broadcast} } 8 | sub targeting { $_[0]->{targeting} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/NumberOfSentMessages.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::NumberOfSentMessages; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub status { $_[0]->{status} } 7 | sub success { $_[0]->{success} } 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Profile.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Profile; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub display_name { $_[0]->{displayName} } 7 | sub user_id { $_[0]->{userId} } 8 | sub picture_url { $_[0]->{pictureUrl} } 9 | sub status_message { $_[0]->{statusMessage} } 10 | sub language { $_[0]->{language} } 11 | 12 | 1; 13 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/RichMenu.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::RichMenu; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent 'LINE::Bot::API::Response::Common'; 6 | 7 | 8 | =head1 NAME 9 | 10 | LINE::Bot::API::Response::RichMenu 11 | 12 | =head1 DESCRIPTION 13 | 14 | This class correspond to the "Rich menu response object" as described in 15 | this page: L 16 | 17 | There is a method for each top-level properties, but values become 18 | simple perl variablse. Num, Str, HashRef, or ArrayRef -- instead of 19 | being objects all the way down. 20 | 21 | =cut 22 | 23 | sub richMenuId { $_[0]->{richMenuId} } 24 | sub chatBarText { $_[0]->{chatBarText} } 25 | sub size { $_[0]->{size} } 26 | sub selected { $_[0]->{selected} } 27 | sub name { $_[0]->{nam} } 28 | sub areas { $_[0]->{areas} } 29 | 30 | # Aliases 31 | sub rich_menu_id { $_[0]->{richMenuId} } 32 | sub chat_bar_text { $_[0]->{chatBarText} } 33 | 34 | 1; 35 | 36 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/RichMenuList.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::RichMenuList; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent 'LINE::Bot::API::Response::Common'; 6 | 7 | sub richmenus { $_[0]->{richmenus} } 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/RoomMemberProfile.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::RoomMemberProfile; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub display_name { $_[0]->{displayName} } 7 | sub user_id { $_[0]->{userId} } 8 | sub picture_url { $_[0]->{pictureUrl} } 9 | 10 | 1; 11 | 12 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/TargetLimit.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::TargetLimit; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub type { $_[0]->{type} } 7 | sub value { $_[0]->{value} } 8 | 9 | 1; 10 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/Token.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::Token; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent 'LINE::Bot::API::Response::Common'; 6 | 7 | sub access_token { $_[0]->{access_token} } 8 | sub expires_in { $_[0]->{expires_in} } 9 | sub token_type { $_[0]->{token_type} } 10 | sub key_id { $_[0]->{key_id} } 11 | 12 | sub key_ids { $_[0]->{key_ids}} 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/TotalUsage.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::TotalUsage; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub total_usage { $_[0]->{totalUsage} } 7 | 8 | 1; 9 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/UserInteractionStatistics.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::UserInteractionStatistics; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub overview { $_[0]->{overview} } 7 | sub messages { $_[0]->{messages} } 8 | sub clicks { $_[0]->{clicks} } 9 | 10 | 1; 11 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/WebhookInformation.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::WebhookInformation; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub endpoint { $_[0]->{endpoint} } 7 | sub active { $_[0]->{active} } 8 | 9 | 1; 10 | 11 | __END__ 12 | 13 | =head1 NAME 14 | 15 | LINE::Bot::API::Response::WebhookInformation 16 | 17 | =head1 DESCRIPTION 18 | 19 | This cllass corresponds to the response object of "Get Webhook Endpoint Information" API as described in this page: L. 20 | 21 | For each top-level properties, there is a corresponding method with the same name which provides access to the value of the property. 22 | 23 | =head1 METHODS 24 | 25 | =over 4 26 | 27 | =item endpoint 28 | 29 | =item active 30 | 31 | =back 32 | -------------------------------------------------------------------------------- /lib/LINE/Bot/API/Response/WebhookTest.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::API::Response::WebhookTest; 2 | use strict; 3 | use warnings; 4 | use parent 'LINE::Bot::API::Response::Common'; 5 | 6 | sub success { $_[0]->{success} } 7 | sub timestamp { $_[0]->{timestamp} } 8 | sub statusCode { $_[0]->{statusCode} } 9 | sub reason { $_[0]->{reason} } 10 | sub detail { $_[0]->{detail} } 11 | 12 | 1; 13 | 14 | __END__ 15 | 16 | =head1 NAME 17 | 18 | LINE::Bot::API::Response::WebhookTest 19 | 20 | =head1 DESCRIPTION 21 | 22 | This cllass corresponds to the response object of "Test Webhook Endpoint" API as described in this page: L 23 | 24 | For each top-level properties, there is a corresponding method with the same name which provides access to the value of the property. 25 | 26 | =head1 METHODS 27 | 28 | =over 4 29 | 30 | =item success 31 | 32 | =item timestamp 33 | 34 | =item statusCode 35 | 36 | =item reason 37 | 38 | =item detail 39 | 40 | =back 41 | -------------------------------------------------------------------------------- /lib/LINE/Bot/Message/Narrowcast.pm: -------------------------------------------------------------------------------- 1 | package LINE::Bot::Message::Narrowcast; 2 | use strict; 3 | use warnings; 4 | 5 | use LINE::Bot::API::Client; 6 | use LINE::Bot::API::Response::NarrowcastStatus; 7 | 8 | use constant { 9 | DEFAULT_MESSAGING_API_ENDPOINT => 'https://api.line.me/v2/bot/', 10 | }; 11 | 12 | sub new { 13 | my ($class, %args) = @_; 14 | 15 | my $client = LINE::Bot::API::Client->new(%args); 16 | bless { 17 | client => $client, 18 | channel_secret => $args{channel_secret}, 19 | channel_access_token => $args{channel_access_token}, 20 | messaging_api_endpoint => $args{messaging_api_endpoint} // DEFAULT_MESSAGING_API_ENDPOINT, 21 | }, $class; 22 | } 23 | 24 | sub request { 25 | my ($self, $method, $path, @payload) = @_; 26 | 27 | return $self->{client}->$method( 28 | $self->{messaging_api_endpoint} . $path, 29 | @payload, 30 | ); 31 | } 32 | 33 | sub send_message { 34 | my ($self, $messages, $recipient, $demographic, $limit, $options) = @_; 35 | 36 | my @headers = (); 37 | if ($options && defined($options->{'retry_key'})) { 38 | push @headers, 'X-Line-Retry-Key' => $options->{'retry_key'}; 39 | } 40 | 41 | my $res = $self->request( 42 | post => 'message/narrowcast', 43 | \@headers, 44 | +{ 45 | messages => $messages, 46 | recipient => $recipient, 47 | filter => { 48 | demographic => $demographic, 49 | }, 50 | limit => $limit, 51 | }, 52 | ); 53 | 54 | LINE::Bot::API::Response::Common->new(%{ $res }); 55 | } 56 | 57 | sub get_narrowcast_message_status { 58 | my ($self, $request_id) = @_; 59 | 60 | my $res = $self->request( 61 | get => "message/progress/narrowcast?requestId=${request_id}" 62 | ); 63 | 64 | LINE::Bot::API::Response::NarrowcastStatus->new(%{ $res }); 65 | } 66 | 67 | 1; 68 | __END__ 69 | 70 | =head1 NAME 71 | 72 | LINE::Bot::Message::Narrowcast 73 | 74 | =head2 Methods 75 | 76 | =head3 C<< send_message($messages, $recipient, $demographic, $limit, $options) >> 77 | 78 | Sends a push message to multiple users. 79 | 80 | C<$message> is a HashRef with key/values for as specified in API reference of this method: L. nested keys are represented in the dotted notations. 81 | 82 | C<$recipient> should be an HashRef with keys/values as specified in the documentation of L. It can be either audience object or redelivery object. You can specify up to 10 recipients per request based on a combination of criteria using logical operator objects. 83 | 84 | C<$demographic> should be an HashRef with key/values as specified in the documentation of L. It represent criteria (e.g. age, gender, OS, region, and friendship duration) on which to filter the list of recipients. You can filter recipients based on a combination of different criteria using logical operator objects. 85 | 86 | C<$limit> should be an HashRef with these optional key-value pairs: 87 | 88 | max: Number 89 | upToRemainingQuota: Boolean 90 | 91 | For example: 92 | 93 | { 94 | "max" => 42, 95 | "upToRemainingQuota": JSON::true, 96 | } 97 | 98 | Noted here that the value for "upToRemainingQuotae" must be one of the boolean values recognizied by L. See also L. 99 | 100 | Messages cannot be sent to groups or rooms. 101 | 102 | The last parameter C<$options> is an HashRef with a list of key-values 103 | pairs to fine-tune the behaviour of this message. At the moment, the 104 | only defined configurable option is C<"retry_key">, which requires an 105 | UUID string for its value. See the section L for the meaning of this particular option. 106 | 107 | =head3 C<< get_narrowcast_message_status($request_id) >> 108 | 109 | Gets the status of a narrowcast message. 110 | 111 | See also the API reference of this method: L 112 | 113 | =cut 114 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More tests => 1; 3 | 4 | BEGIN { use_ok 'LINE::Bot::API' } 5 | -------------------------------------------------------------------------------- /t/00_send_message_response.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use lib 't/lib'; 3 | use t::Util; 4 | 5 | use LINE::Bot::API; 6 | use LINE::Bot::API::Builder::SendMessage; 7 | 8 | my $bot = LINE::Bot::API->new( 9 | channel_secret => 'testsecret', 10 | channel_access_token => 'ACCESS_TOKEN', 11 | ); 12 | 13 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 14 | $builder->add_text( text => 'hello!' ); 15 | 16 | subtest 'success' => sub { 17 | send_request { 18 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 19 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 20 | ok $res->is_success; 21 | is $res->http_status, 200; 22 | is $res->x_line_request_id, 'dummy_id'; 23 | } receive_request { 24 | +{}; 25 | }; 26 | }; 27 | 28 | subtest 'fail' => sub { 29 | send_request { 30 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 31 | isa_ok $res, 'LINE::Bot::API::Response::Error'; 32 | ok !$res->is_success; 33 | is $res->http_status(), 500; 34 | is $res->message(), 'ISE'; 35 | is $res->details(), array { 36 | item hash { 37 | field message => 'detail message'; 38 | }; 39 | end(); 40 | }; 41 | } receive_request { 42 | +{ 43 | http_status => 500, 44 | message => 'ISE', 45 | details => [ 46 | +{ message => 'detail message' } 47 | ], 48 | }; 49 | }; 50 | }; 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t/01_send_text-with-emoji.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | 3 | use lib 't/lib'; 4 | use t::Util; 5 | 6 | use JSON::XS; 7 | use LINE::Bot::API; 8 | use LINE::Bot::API::Builder::SendMessage; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 16 | 17 | # The example from https://developers.line.biz/en/reference/messaging-api/#text-message 18 | $builder->add_text( 19 | text => '$ LINE emoji $', 20 | emojis => [ 21 | +{ 22 | "index" => 0, 23 | "productId" => "5ac1bfd5040ab15980c9b435", 24 | "emojiId" => "001" 25 | }, 26 | +{ 27 | "index" => 13, 28 | "productId" => "5ac1bfd5040ab15980c9b435", 29 | "emojiId" => "002" 30 | } 31 | ] 32 | ); 33 | 34 | send_request { 35 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 36 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 37 | ok $res->is_success; 38 | is $res->http_status, 200; 39 | } receive_request { 40 | my %args = @_; 41 | is $args{method}, 'POST'; 42 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 43 | 44 | my $data = decode_json $args{content}; 45 | is $data->{to}, 'DUMMY_ID'; 46 | is scalar(@{ $data->{messages} }), 1; 47 | my $message = $data->{messages}[0]; 48 | is $message->{type}, 'text'; 49 | is $message->{text}, '$ LINE emoji $'; 50 | ok defined($message->{emojis}); 51 | is ref($message->{emojis}), 'ARRAY'; 52 | is ((0+ @{$message->{emojis}}), 2); 53 | 54 | for my $emoji (@{$message->{emojis}}) { 55 | is 0+keys(%$emoji), 3; 56 | ok defined($emoji->{index}); 57 | ok defined($emoji->{productId}); 58 | ok defined($emoji->{emojiId}); 59 | } 60 | 61 | my $has_header = 0; 62 | my @headers = @{ $args{headers} }; 63 | while (my($key, $value) = splice @headers, 0, 2) { 64 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 65 | } 66 | is $has_header, 1; 67 | 68 | +{}; 69 | }; 70 | 71 | send_request { 72 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 73 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 74 | ok $res->is_success; 75 | is $res->http_status, 200; 76 | } receive_request { 77 | my %args = @_; 78 | is $args{method}, 'POST'; 79 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 80 | 81 | my $data = decode_json $args{content}; 82 | is $data->{replyToken}, 'DUMMY_TOKEN'; 83 | is scalar(@{ $data->{messages} }), 1; 84 | my $message = $data->{messages}[0]; 85 | is $message->{type}, 'text'; 86 | ok defined( $message->{emojis} ); 87 | 88 | my $has_header = 0; 89 | my @headers = @{ $args{headers} }; 90 | while (my($key, $value) = splice @headers, 0, 2) { 91 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 92 | } 93 | is $has_header, 1; 94 | 95 | +{}; 96 | }; 97 | 98 | done_testing; 99 | -------------------------------------------------------------------------------- /t/01_send_text.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_text( text => 'hello!' ); 18 | 19 | send_request { 20 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 21 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | } receive_request { 25 | my %args = @_; 26 | is $args{method}, 'POST'; 27 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 28 | 29 | my $data = decode_json $args{content}; 30 | is $data->{to}, 'DUMMY_ID'; 31 | is scalar(@{ $data->{messages} }), 1; 32 | my $message = $data->{messages}[0]; 33 | is $message->{type}, 'text'; 34 | is $message->{text}, 'hello!'; 35 | 36 | my $has_header = 0; 37 | my @headers = @{ $args{headers} }; 38 | while (my($key, $value) = splice @headers, 0, 2) { 39 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 40 | } 41 | is $has_header, 1; 42 | 43 | +{}; 44 | }; 45 | 46 | send_request { 47 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 48 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 49 | ok $res->is_success; 50 | is $res->http_status, 200; 51 | } receive_request { 52 | my %args = @_; 53 | is $args{method}, 'POST'; 54 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 55 | 56 | my $data = decode_json $args{content}; 57 | is $data->{replyToken}, 'DUMMY_TOKEN'; 58 | is scalar(@{ $data->{messages} }), 1; 59 | my $message = $data->{messages}[0]; 60 | is $message->{type}, 'text'; 61 | is $message->{text}, 'hello!'; 62 | 63 | my $has_header = 0; 64 | my @headers = @{ $args{headers} }; 65 | while (my($key, $value) = splice @headers, 0, 2) { 66 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 67 | } 68 | is $has_header, 1; 69 | 70 | +{}; 71 | }; 72 | 73 | done_testing; 74 | -------------------------------------------------------------------------------- /t/02_send_image.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_image( 18 | image_url => 'http://example.com/image.jpg', 19 | preview_url => 'http://example.com/image_preview.jpg', 20 | ); 21 | 22 | send_request { 23 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 30 | 31 | my $data = decode_json $args{content}; 32 | is $data->{to}, 'DUMMY_ID'; 33 | is scalar(@{ $data->{messages} }), 1; 34 | my $message = $data->{messages}[0]; 35 | is $message->{type}, 'image'; 36 | is $message->{originalContentUrl}, 'http://example.com/image.jpg'; 37 | is $message->{previewImageUrl}, 'http://example.com/image_preview.jpg'; 38 | 39 | my $has_header = 0; 40 | my @headers = @{ $args{headers} }; 41 | while (my($key, $value) = splice @headers, 0, 2) { 42 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 43 | } 44 | is $has_header, 1; 45 | 46 | +{}; 47 | }; 48 | 49 | send_request { 50 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 51 | ok $res->is_success; 52 | is $res->http_status, 200; 53 | } receive_request { 54 | my %args = @_; 55 | is $args{method}, 'POST'; 56 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 57 | 58 | my $data = decode_json $args{content}; 59 | is $data->{replyToken}, 'DUMMY_TOKEN'; 60 | is scalar(@{ $data->{messages} }), 1; 61 | my $message = $data->{messages}[0]; 62 | is $message->{type}, 'image'; 63 | is $message->{originalContentUrl}, 'http://example.com/image.jpg'; 64 | is $message->{previewImageUrl}, 'http://example.com/image_preview.jpg'; 65 | 66 | my $has_header = 0; 67 | my @headers = @{ $args{headers} }; 68 | while (my($key, $value) = splice @headers, 0, 2) { 69 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 70 | } 71 | is $has_header, 1; 72 | 73 | +{}; 74 | }; 75 | 76 | done_testing; 77 | -------------------------------------------------------------------------------- /t/03_send_video.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_video( 18 | video_url => 'http://example.com/image.mp4', 19 | preview_url => 'http://example.com/image_preview.jpg', 20 | ); 21 | 22 | send_request { 23 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 30 | 31 | my $data = decode_json $args{content}; 32 | is $data->{to}, 'DUMMY_ID'; 33 | is scalar(@{ $data->{messages} }), 1; 34 | my $message = $data->{messages}[0]; 35 | is $message->{type}, 'video'; 36 | is $message->{originalContentUrl}, 'http://example.com/image.mp4'; 37 | is $message->{previewImageUrl}, 'http://example.com/image_preview.jpg'; 38 | 39 | my $has_header = 0; 40 | my @headers = @{ $args{headers} }; 41 | while (my($key, $value) = splice @headers, 0, 2) { 42 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 43 | } 44 | is $has_header, 1; 45 | 46 | +{}; 47 | }; 48 | 49 | send_request { 50 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 51 | ok $res->is_success; 52 | is $res->http_status, 200; 53 | } receive_request { 54 | my %args = @_; 55 | is $args{method}, 'POST'; 56 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 57 | 58 | my $data = decode_json $args{content}; 59 | is $data->{replyToken}, 'DUMMY_TOKEN'; 60 | is scalar(@{ $data->{messages} }), 1; 61 | my $message = $data->{messages}[0]; 62 | is $message->{type}, 'video'; 63 | is $message->{originalContentUrl}, 'http://example.com/image.mp4'; 64 | is $message->{previewImageUrl}, 'http://example.com/image_preview.jpg'; 65 | 66 | my $has_header = 0; 67 | my @headers = @{ $args{headers} }; 68 | while (my($key, $value) = splice @headers, 0, 2) { 69 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 70 | } 71 | is $has_header, 1; 72 | 73 | +{}; 74 | }; 75 | 76 | done_testing; 77 | -------------------------------------------------------------------------------- /t/04_send_audio.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_audio( 18 | audio_url => 'http://example.com/image.mp4', 19 | duration => 12_000, 20 | ); 21 | 22 | send_request { 23 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 30 | 31 | my $data = decode_json $args{content}; 32 | is $data->{to}, 'DUMMY_ID'; 33 | is scalar(@{ $data->{messages} }), 1; 34 | my $message = $data->{messages}[0]; 35 | is $message->{type}, 'audio'; 36 | is $message->{originalContentUrl}, 'http://example.com/image.mp4'; 37 | is $message->{duration}, 12_000; 38 | 39 | my $has_header = 0; 40 | my @headers = @{ $args{headers} }; 41 | while (my($key, $value) = splice @headers, 0, 2) { 42 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 43 | } 44 | is $has_header, 1; 45 | 46 | +{}; 47 | }; 48 | 49 | send_request { 50 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 51 | ok $res->is_success; 52 | is $res->http_status, 200; 53 | } receive_request { 54 | my %args = @_; 55 | is $args{method}, 'POST'; 56 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 57 | 58 | my $data = decode_json $args{content}; 59 | is $data->{replyToken}, 'DUMMY_TOKEN'; 60 | is scalar(@{ $data->{messages} }), 1; 61 | my $message = $data->{messages}[0]; 62 | is $message->{type}, 'audio'; 63 | is $message->{originalContentUrl}, 'http://example.com/image.mp4'; 64 | is $message->{duration}, 12_000; 65 | 66 | my $has_header = 0; 67 | my @headers = @{ $args{headers} }; 68 | while (my($key, $value) = splice @headers, 0, 2) { 69 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 70 | } 71 | is $has_header, 1; 72 | 73 | +{}; 74 | }; 75 | 76 | done_testing; 77 | -------------------------------------------------------------------------------- /t/05_send_location.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_location( 18 | title => 'location label', 19 | address => 'tokyo shibuya-ku', 20 | latitude => -35.10, 21 | longitude => 139.10, 22 | ); 23 | 24 | send_request { 25 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 26 | ok $res->is_success; 27 | is $res->http_status, 200; 28 | } receive_request { 29 | my %args = @_; 30 | is $args{method}, 'POST'; 31 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 32 | 33 | my $data = decode_json $args{content}; 34 | is $data->{to}, 'DUMMY_ID'; 35 | is scalar(@{ $data->{messages} }), 1; 36 | my $message = $data->{messages}[0]; 37 | is $message->{type}, 'location'; 38 | is $message->{title}, 'location label'; 39 | is $message->{address}, 'tokyo shibuya-ku'; 40 | is $message->{latitude}, -35.10; 41 | is $message->{longitude}, 139.10; 42 | 43 | my $has_header = 0; 44 | my @headers = @{ $args{headers} }; 45 | while (my($key, $value) = splice @headers, 0, 2) { 46 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 47 | } 48 | is $has_header, 1; 49 | 50 | +{}; 51 | }; 52 | 53 | send_request { 54 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 55 | ok $res->is_success; 56 | is $res->http_status, 200; 57 | } receive_request { 58 | my %args = @_; 59 | is $args{method}, 'POST'; 60 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 61 | 62 | my $data = decode_json $args{content}; 63 | is $data->{replyToken}, 'DUMMY_TOKEN'; 64 | is scalar(@{ $data->{messages} }), 1; 65 | my $message = $data->{messages}[0]; 66 | is $message->{type}, 'location'; 67 | is $message->{title}, 'location label'; 68 | is $message->{address}, 'tokyo shibuya-ku'; 69 | is $message->{latitude}, -35.10; 70 | is $message->{longitude}, 139.10; 71 | 72 | my $has_header = 0; 73 | my @headers = @{ $args{headers} }; 74 | while (my($key, $value) = splice @headers, 0, 2) { 75 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 76 | } 77 | is $has_header, 1; 78 | 79 | +{}; 80 | }; 81 | 82 | done_testing; 83 | -------------------------------------------------------------------------------- /t/06_send_sticker.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS; 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | my $builder = LINE::Bot::API::Builder::SendMessage->new; 17 | $builder->add_sticker( 18 | package_id => '1', 19 | sticker_id => '2', 20 | ); 21 | 22 | send_request { 23 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 30 | 31 | my $data = decode_json $args{content}; 32 | is $data->{to}, 'DUMMY_ID'; 33 | is scalar(@{ $data->{messages} }), 1; 34 | my $message = $data->{messages}[0]; 35 | is $message->{type}, 'sticker'; 36 | is $message->{packageId}, '1'; 37 | is $message->{stickerId}, '2'; 38 | 39 | my $has_header = 0; 40 | my @headers = @{ $args{headers} }; 41 | while (my($key, $value) = splice @headers, 0, 2) { 42 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 43 | } 44 | is $has_header, 1; 45 | 46 | +{}; 47 | }; 48 | 49 | send_request { 50 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 51 | ok $res->is_success; 52 | is $res->http_status, 200; 53 | } receive_request { 54 | my %args = @_; 55 | is $args{method}, 'POST'; 56 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 57 | 58 | my $data = decode_json $args{content}; 59 | is $data->{replyToken}, 'DUMMY_TOKEN'; 60 | is scalar(@{ $data->{messages} }), 1; 61 | my $message = $data->{messages}[0]; 62 | is $message->{type}, 'sticker'; 63 | is $message->{packageId}, '1'; 64 | is $message->{stickerId}, '2'; 65 | 66 | my $has_header = 0; 67 | my @headers = @{ $args{headers} }; 68 | while (my($key, $value) = splice @headers, 0, 2) { 69 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 70 | } 71 | is $has_header, 1; 72 | 73 | +{}; 74 | }; 75 | 76 | done_testing; 77 | -------------------------------------------------------------------------------- /t/11_send_multiple.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use JSON::XS; 9 | use LINE::Bot::API; 10 | use LINE::Bot::API::Builder::SendMessage; 11 | 12 | my $bot = LINE::Bot::API->new( 13 | channel_secret => 'testsecret', 14 | channel_access_token => 'ACCESS_TOKEN', 15 | ); 16 | 17 | my $builder = LINE::Bot::API::Builder::SendMessage->new 18 | ->add_text( text => '一' ) 19 | ->add_text( text => '二' ) 20 | ->add_text( text => '三' ) 21 | ->add_text( text => '四' ) 22 | ->add_text( text => '五' ); 23 | 24 | send_request { 25 | my $res = $bot->push_message('DUMMY_ID', $builder->build); 26 | ok $res->is_success; 27 | is $res->http_status, 200; 28 | } receive_request { 29 | my %args = @_; 30 | is $args{method}, 'POST'; 31 | is $args{url}, 'https://api.line.me/v2/bot/message/push'; 32 | 33 | my $data = decode_json $args{content}; 34 | is $data->{to}, 'DUMMY_ID'; 35 | is scalar(@{ $data->{messages} }), 5; 36 | { 37 | my $message = $data->{messages}[0]; 38 | is $message->{type}, 'text'; 39 | is $message->{text}, '一'; 40 | } 41 | { 42 | my $message = $data->{messages}[1]; 43 | is $message->{type}, 'text'; 44 | is $message->{text}, '二'; 45 | } 46 | { 47 | my $message = $data->{messages}[2]; 48 | is $message->{type}, 'text'; 49 | is $message->{text}, '三'; 50 | } 51 | { 52 | my $message = $data->{messages}[3]; 53 | is $message->{type}, 'text'; 54 | is $message->{text}, '四'; 55 | } 56 | { 57 | my $message = $data->{messages}[4]; 58 | is $message->{type}, 'text'; 59 | is $message->{text}, '五'; 60 | } 61 | 62 | my $has_header = 0; 63 | my @headers = @{ $args{headers} }; 64 | while (my($key, $value) = splice @headers, 0, 2) { 65 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 66 | } 67 | is $has_header, 1; 68 | 69 | +{}; 70 | }; 71 | 72 | send_request { 73 | my $res = $bot->reply_message('DUMMY_TOKEN', $builder->build); 74 | ok $res->is_success; 75 | is $res->http_status, 200; 76 | } receive_request { 77 | my %args = @_; 78 | is $args{method}, 'POST'; 79 | is $args{url}, 'https://api.line.me/v2/bot/message/reply'; 80 | 81 | my $data = decode_json $args{content}; 82 | is $data->{replyToken}, 'DUMMY_TOKEN'; 83 | is scalar(@{ $data->{messages} }), 5; 84 | { 85 | my $message = $data->{messages}[0]; 86 | is $message->{type}, 'text'; 87 | is $message->{text}, '一'; 88 | } 89 | { 90 | my $message = $data->{messages}[1]; 91 | is $message->{type}, 'text'; 92 | is $message->{text}, '二'; 93 | } 94 | { 95 | my $message = $data->{messages}[2]; 96 | is $message->{type}, 'text'; 97 | is $message->{text}, '三'; 98 | } 99 | { 100 | my $message = $data->{messages}[3]; 101 | is $message->{type}, 'text'; 102 | is $message->{text}, '四'; 103 | } 104 | { 105 | my $message = $data->{messages}[4]; 106 | is $message->{type}, 'text'; 107 | is $message->{text}, '五'; 108 | } 109 | 110 | my $has_header = 0; 111 | my @headers = @{ $args{headers} }; 112 | while (my($key, $value) = splice @headers, 0, 2) { 113 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 114 | } 115 | is $has_header, 1; 116 | 117 | +{}; 118 | }; 119 | 120 | done_testing; 121 | -------------------------------------------------------------------------------- /t/12_get_number_of_send_messages.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | # get_number_of_sent_reply_messages 20 | send_request { 21 | my $res = $bot->get_number_of_sent_reply_messages('20190126'); 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | is $res->status, 'ready'; 25 | is $res->success, 495; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'GET'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/delivery/reply?date=20190126'; 30 | 31 | my $has_header = 0; 32 | my @headers = @{ $args{headers} }; 33 | while (my($key, $value) = splice @headers, 0, 2) { 34 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 35 | } 36 | is $has_header, 1; 37 | 38 | +{ 39 | status => 'ready', 40 | success => 495, 41 | }; 42 | }; 43 | 44 | # get_number_of_sent_push_messages 45 | send_request { 46 | my $res = $bot->get_number_of_sent_push_messages('20190126'); 47 | ok $res->is_success; 48 | is $res->http_status, 200; 49 | is $res->status, 'ready'; 50 | is $res->success, 495; 51 | } receive_request { 52 | my %args = @_; 53 | is $args{method}, 'GET'; 54 | is $args{url}, 'https://api.line.me/v2/bot/message/delivery/push?date=20190126'; 55 | 56 | my $has_header = 0; 57 | my @headers = @{ $args{headers} }; 58 | while (my($key, $value) = splice @headers, 0, 2) { 59 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 60 | } 61 | is $has_header, 1; 62 | 63 | +{ 64 | status => 'ready', 65 | success => 495, 66 | }; 67 | }; 68 | 69 | # get_number_of_sent_multicast_messages 70 | send_request { 71 | my $res = $bot->get_number_of_sent_multicast_messages('20190126'); 72 | ok $res->is_success; 73 | is $res->http_status, 200; 74 | is $res->status, 'ready'; 75 | is $res->success, 495; 76 | } receive_request { 77 | my %args = @_; 78 | is $args{method}, 'GET'; 79 | is $args{url}, 'https://api.line.me/v2/bot/message/delivery/multicast?date=20190126'; 80 | 81 | my $has_header = 0; 82 | my @headers = @{ $args{headers} }; 83 | while (my($key, $value) = splice @headers, 0, 2) { 84 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 85 | } 86 | is $has_header, 1; 87 | 88 | +{ 89 | status => 'ready', 90 | success => 495, 91 | }; 92 | }; 93 | 94 | # get_number_of_send_broadcast_messages 95 | send_request { 96 | my $res = $bot->get_number_of_send_broadcast_messages('20190126'); 97 | ok $res->is_success; 98 | is $res->http_status, 200; 99 | is $res->status, 'ready'; 100 | is $res->success, 10000; 101 | } receive_request { 102 | my %args = @_; 103 | is $args{method}, 'GET'; 104 | is $args{url}, 'https://api.line.me/v2/bot/message/delivery/broadcast?date=20190126'; 105 | 106 | my $has_header = 0; 107 | my @headers = @{ $args{headers} }; 108 | while (my($key, $value) = splice @headers, 0, 2) { 109 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 110 | } 111 | is $has_header, 1; 112 | 113 | +{ 114 | status => 'ready', 115 | success => 10000, 116 | }; 117 | }; 118 | 119 | done_testing; 120 | -------------------------------------------------------------------------------- /t/12_multicast.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use JSON::XS; 9 | use LINE::Bot::API; 10 | use LINE::Bot::API::Builder::SendMessage; 11 | 12 | my $bot = LINE::Bot::API->new( 13 | channel_secret => 'testsecret', 14 | channel_access_token => 'ACCESS_TOKEN', 15 | ); 16 | 17 | my $builder = LINE::Bot::API::Builder::SendMessage->new 18 | ->add_text( text => 'one' ) 19 | ->add_text( text => 'two' ); 20 | 21 | send_request { 22 | my $res = $bot->multicast(['DUMMY_ID_1', 'DUMMY_ID_2'], $builder->build); 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'POST'; 28 | is $args{url}, 'https://api.line.me/v2/bot/message/multicast'; 29 | 30 | my $data = decode_json $args{content}; 31 | is_deeply $data->{to}, ['DUMMY_ID_1', 'DUMMY_ID_2']; 32 | is scalar(@{ $data->{messages} }), 2; 33 | { 34 | my $message = $data->{messages}[0]; 35 | is $message->{type}, 'text'; 36 | is $message->{text}, 'one'; 37 | } 38 | { 39 | my $message = $data->{messages}[1]; 40 | is $message->{type}, 'text'; 41 | is $message->{text}, 'two'; 42 | } 43 | 44 | my $has_header = 0; 45 | my @headers = @{ $args{headers} }; 46 | while (my($key, $value) = splice @headers, 0, 2) { 47 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 48 | } 49 | is $has_header, 1; 50 | 51 | +{}; 52 | }; 53 | 54 | done_testing; 55 | -------------------------------------------------------------------------------- /t/13_broadcast.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use JSON::XS; 9 | use LINE::Bot::API; 10 | use LINE::Bot::API::Builder::SendMessage; 11 | 12 | my $bot = LINE::Bot::API->new( 13 | channel_secret => 'testsecret', 14 | channel_access_token => 'ACCESS_TOKEN', 15 | ); 16 | 17 | my $builder = LINE::Bot::API::Builder::SendMessage->new 18 | ->add_text( text => 'one' ) 19 | ->add_text( text => 'two' ); 20 | 21 | send_request { 22 | my $res = $bot->broadcast($builder->build); 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'POST'; 28 | is $args{url}, 'https://api.line.me/v2/bot/message/broadcast'; 29 | 30 | my $data = decode_json $args{content}; 31 | is scalar(@{ $data->{messages} }), 2; 32 | { 33 | my $message = $data->{messages}[0]; 34 | is $message->{type}, 'text'; 35 | is $message->{text}, 'one'; 36 | } 37 | { 38 | my $message = $data->{messages}[1]; 39 | is $message->{type}, 'text'; 40 | is $message->{text}, 'two'; 41 | } 42 | 43 | my $has_header = 0; 44 | my @headers = @{ $args{headers} }; 45 | while (my($key, $value) = splice @headers, 0, 2) { 46 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 47 | } 48 | is $has_header, 1; 49 | 50 | +{}; 51 | }; 52 | 53 | done_testing; 54 | -------------------------------------------------------------------------------- /t/14_get_friend_demographics.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use JSON::XS; 11 | use Carp (); 12 | 13 | $SIG{__DIE__} = \&Carp::confess; 14 | 15 | my $bot = LINE::Bot::API->new( 16 | channel_secret => 'testsecret', 17 | channel_access_token => 'ACCESS_TOKEN', 18 | ); 19 | 20 | # get_number_of_messages_sent_this_month 21 | send_request { 22 | my $res = $bot->get_friend_demographics; 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | 26 | ok $res->available; 27 | is $res->genders->[0]->{gender}, 'unknown'; 28 | is $res->genders->[0]->{percentage}, 37.6; 29 | is $res->genders->[1]->{gender}, 'male'; 30 | is $res->genders->[1]->{percentage}, 31.8; 31 | is $res->genders->[2]->{gender}, 'female'; 32 | is $res->genders->[2]->{percentage}, 30.6; 33 | is $res->ages->[0]->{age}, 'unknown'; 34 | is $res->ages->[0]->{percentage}, 37.6; 35 | is $res->ages->[1]->{age}, 'from50'; 36 | is $res->ages->[1]->{percentage}, 17.3; 37 | is $res->areas->[0]->{area}, 'unknown'; 38 | is $res->areas->[0]->{percentage}, 42.9; 39 | is $res->areas->[1]->{area}, '徳島'; 40 | is $res->areas->[1]->{percentage}, 2.9; 41 | is $res->appTypes->[0]->{appType}, 'ios'; 42 | is $res->appTypes->[0]->{percentage}, 62.4; 43 | is $res->appTypes->[1]->{appType}, 'android'; 44 | is $res->appTypes->[1]->{percentage}, 27.7; 45 | is $res->appTypes->[2]->{appType}, 'others'; 46 | is $res->appTypes->[2]->{percentage}, 9.9; 47 | is $res->subscriptionPeriods->[0]->{subscriptionPeriod}, 'over365days'; 48 | is $res->subscriptionPeriods->[0]->{percentage}, 96.4; 49 | is $res->subscriptionPeriods->[1]->{subscriptionPeriod}, 'within365days'; 50 | is $res->subscriptionPeriods->[1]->{percentage}, 1.9; 51 | is $res->subscriptionPeriods->[2]->{subscriptionPeriod}, 'within180days'; 52 | is $res->subscriptionPeriods->[2]->{percentage}, 1.2; 53 | is $res->subscriptionPeriods->[3]->{subscriptionPeriod}, 'within90days'; 54 | is $res->subscriptionPeriods->[3]->{percentage}, 0.5; 55 | is $res->subscriptionPeriods->[4]->{subscriptionPeriod}, 'within30days'; 56 | is $res->subscriptionPeriods->[4]->{percentage}, 0.1; 57 | is $res->subscriptionPeriods->[5]->{subscriptionPeriod}, 'within7days'; 58 | is $res->subscriptionPeriods->[5]->{percentage}, 0; 59 | } receive_request { 60 | my %args = @_; 61 | is $args{method}, 'GET'; 62 | is $args{url}, 'https://api.line.me/v2/bot/insight/demographic'; 63 | 64 | my $has_header = 0; 65 | my @headers = @{ $args{headers} }; 66 | while (my($key, $value) = splice @headers, 0, 2) { 67 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 68 | } 69 | is $has_header, 1; 70 | 71 | +{ 72 | available => JSON::XS::true, 73 | genders => [ 74 | { 75 | gender => 'unknown', 76 | percentage => 37.6, 77 | }, 78 | { 79 | gender => 'male', 80 | percentage => 31.8, 81 | }, 82 | { 83 | gender => 'female', 84 | percentage => 30.6 85 | }, 86 | ], 87 | ages => [ 88 | { 89 | age => 'unknown', 90 | percentage => 37.6, 91 | }, 92 | { 93 | age => 'from50', 94 | percentage => 17.3, 95 | }, 96 | ], 97 | areas => [ 98 | { 99 | area => "unknown", 100 | percentage => 42.9, 101 | }, 102 | { 103 | area => "徳島", 104 | percentage => 2.9, 105 | }, 106 | ], 107 | appTypes => [ 108 | { 109 | appType => 'ios', 110 | percentage => 62.4, 111 | }, 112 | { 113 | appType => 'android', 114 | percentage => 27.7, 115 | }, 116 | { 117 | appType => 'others', 118 | percentage => 9.9, 119 | }, 120 | ], 121 | subscriptionPeriods => [ 122 | { 123 | subscriptionPeriod => 'over365days', 124 | percentage => 96.4, 125 | }, 126 | { 127 | subscriptionPeriod => 'within365days', 128 | percentage => 1.9, 129 | }, 130 | { 131 | subscriptionPeriod => 'within180days', 132 | percentage => 1.2, 133 | }, 134 | { 135 | subscriptionPeriod => 'within90days', 136 | percentage => 0.5, 137 | }, 138 | { 139 | subscriptionPeriod => 'within30days', 140 | percentage => 0.1, 141 | }, 142 | { 143 | subscriptionPeriod => 'within7days', 144 | percentage => 0, 145 | }, 146 | ], 147 | }; 148 | }; 149 | 150 | done_testing; 151 | -------------------------------------------------------------------------------- /t/14_get_number_of_message_deliveries.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | # get_number_of_message_deliveries 20 | send_request { 21 | my $res = $bot->get_number_of_message_deliveries({ date => '20200214' }); 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | 25 | is $res->status, 'ready'; 26 | is $res->broadcast, 5385; 27 | is $res->targeting, 522; 28 | } receive_request { 29 | my %args = @_; 30 | is $args{method}, 'GET'; 31 | use Data::Dumper; 32 | print Dumper($args{url}); 33 | is $args{url}, 'https://api.line.me/v2/bot/insight/message/delivery?date=20200214'; 34 | 35 | # this value is example response from LINE developers 36 | +{ 37 | status => 'ready', 38 | broadcast => 5385, 39 | targeting => 522 40 | }; 41 | }; 42 | 43 | done_testing; 44 | -------------------------------------------------------------------------------- /t/14_get_number_of_messages_sent_this_month.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | # get_number_of_messages_sent_this_month 20 | send_request { 21 | my $res = $bot->get_target_limit_for_additional_messages; 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | is $res->type, 'limited'; 25 | is $res->value, 1000; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'GET'; 29 | is $args{url}, 'https://api.line.me/v2/bot/message/quota'; 30 | 31 | my $has_header = 0; 32 | my @headers = @{ $args{headers} }; 33 | while (my($key, $value) = splice @headers, 0, 2) { 34 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 35 | } 36 | is $has_header, 1; 37 | 38 | +{ 39 | type => 'limited', 40 | value => 1000, 41 | }; 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/14_get_user_interaction_statistics.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | # get_user_interaction_statistics 20 | send_request { 21 | my $res = $bot->get_user_interaction_statistics({ requestId => 'f70dd685-499a-4231-a441-f24b8d4fba21' }); 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | 25 | is $res->overview->{requestId}, 'f70dd685-499a-4231-a441-f24b8d4fba21'; 26 | is $res->overview->{timestamp}, 1568214000; 27 | is $res->overview->{delivered}, 32; 28 | is $res->overview->{uniqueImpression}, 4; 29 | is $res->overview->{uniqueClick}, undef; 30 | is $res->overview->{uniqueMediaPlayed}, 2; 31 | is $res->overview->{uniqueMediaPlayed100Percent}, -1; 32 | is $res->messages->[0]->{seq}, 1; 33 | is $res->messages->[0]->{impression}, 18; 34 | is $res->messages->[0]->{mediaPlayed}, 11; 35 | is $res->messages->[0]->{mediaPlayed25Percent}, -1; 36 | is $res->messages->[0]->{mediaPlayed50Percent}, -1; 37 | is $res->messages->[0]->{mediaPlayed75Percent}, -1; 38 | is $res->messages->[0]->{mediaPlayed100Percent}, -1; 39 | is $res->messages->[0]->{uniqueMediaPlayed}, 2; 40 | is $res->messages->[0]->{uniqueMediaPlayed25Percent}, -1; 41 | is $res->messages->[0]->{uniqueMediaPlayed50Percent}, -1; 42 | is $res->messages->[0]->{uniqueMediaPlayed75Percent}, -1; 43 | is $res->messages->[0]->{uniqueMediaPlayed100Percent}, -1; 44 | is $res->clicks->[0]->{seq}, 1; 45 | is $res->clicks->[0]->{url}, 'https://www.yahoo.co.jp/'; 46 | is $res->clicks->[0]->{click}, -1; 47 | is $res->clicks->[0]->{uniqueClick}, -1; 48 | is $res->clicks->[0]->{uniqueClickOfRequest}, -1; 49 | is $res->clicks->[1]->{seq}, 1; 50 | is $res->clicks->[1]->{url}, 'https://www.google.com/?hl=ja'; 51 | is $res->clicks->[1]->{click}, -1; 52 | is $res->clicks->[1]->{uniqueClick}, -1; 53 | is $res->clicks->[1]->{uniqueClickOfRequest}, -1; 54 | } receive_request { 55 | my %args = @_; 56 | is $args{method}, 'GET'; 57 | is $args{url}, 'https://api.line.me/v2/bot/insight/message/event?requestId=f70dd685-499a-4231-a441-f24b8d4fba21'; 58 | 59 | my $has_header = 0; 60 | my @headers = @{ $args{headers} }; 61 | while (my($key, $value) = splice @headers, 0, 2) { 62 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 63 | } 64 | is $has_header, 1; 65 | 66 | +{ 67 | overview => { 68 | requestId => 'f70dd685-499a-4231-a441-f24b8d4fba21', 69 | timestamp => 1568214000, 70 | delivered => 32, 71 | uniqueImpression => 4, 72 | uniqueClick => undef, 73 | uniqueMediaPlayed => 2, 74 | uniqueMediaPlayed100Percent => -1, 75 | }, 76 | messages => [ 77 | { 78 | seq => 1, 79 | impression => 18, 80 | mediaPlayed => 11, 81 | mediaPlayed25Percent => -1, 82 | mediaPlayed50Percent => -1, 83 | mediaPlayed75Percent => -1, 84 | mediaPlayed100Percent => -1, 85 | uniqueMediaPlayed => 2, 86 | uniqueMediaPlayed25Percent => -1, 87 | uniqueMediaPlayed50Percent => -1, 88 | uniqueMediaPlayed75Percent => -1, 89 | uniqueMediaPlayed100Percent => -1, 90 | }, 91 | ], 92 | clicks => [ 93 | { 94 | seq => 1, 95 | url => 'https://www.yahoo.co.jp/', 96 | click => -1, 97 | uniqueClick => -1, 98 | uniqueClickOfRequest => -1, 99 | }, 100 | { 101 | seq => 1, 102 | url => 'https://www.google.com/?hl=ja', 103 | click => -1, 104 | uniqueClick => -1, 105 | uniqueClickOfRequest => -1, 106 | }, 107 | ], 108 | }; 109 | }; 110 | 111 | done_testing; 112 | -------------------------------------------------------------------------------- /t/15_get_target_limit_for_additional_messages.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | # get_number_of_messages_sent_this_month 20 | send_request { 21 | my $res = $bot->get_number_of_messages_sent_this_month; 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | is $res->total_usage, 500; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'GET'; 28 | is $args{url}, 'https://api.line.me/v2/bot/message/quota/consumption'; 29 | 30 | my $has_header = 0; 31 | my @headers = @{ $args{headers} }; 32 | while (my($key, $value) = splice @headers, 0, 2) { 33 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 34 | } 35 | is $has_header, 1; 36 | 37 | +{ 38 | totalUsage => 500, 39 | }; 40 | }; 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/16_create_audience_for_click_based_retargeting.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#create_audience_for_click_based_retartgeting' => sub { 17 | my $content_type = 'application/json'; 18 | 19 | send_request { 20 | my $res = $bot->create_audience_for_click_based_retartgeting({ 21 | description => 'audienceGroupName', 22 | requestId => '12222', 23 | clickUrl => 'https://line.me/en', 24 | }); 25 | ok $res->is_success; 26 | is $res->http_status, 200; 27 | } receive_request { 28 | my %args = @_; 29 | is $args{method}, 'POST'; 30 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/click'; 31 | 32 | my %headers = @{ $args{headers} }; 33 | is $headers{'Content-Type'}, 'application/json'; 34 | 35 | my $content = decode_json($args{content}); 36 | eq_hash $content, { 37 | description => 'audienceGroupName', 38 | requestId => '12222', 39 | clickUrl => 'https://line.me/en', 40 | }; 41 | 42 | +{ 43 | audienceGroupId => 4389303728991, 44 | type => 'CLICK', 45 | description => 'test', 46 | created => 1500351844, 47 | requestId => 'f70dd685-499a-4231-a441-f24b8d4fba21', 48 | clickUrl => undef, 49 | } 50 | } 51 | }; 52 | 53 | done_testing(); 54 | -------------------------------------------------------------------------------- /t/16_create_audience_for_impression_based_retargeting.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#create_audience_for_impression_based_retargeting' => sub { 17 | my $content_type = 'application/json'; 18 | 19 | send_request { 20 | my $res = $bot->create_audience_for_impression_based_retargeting({ 21 | description => 'audienceGroupName', 22 | requestId => '12222', 23 | }); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/imp'; 30 | 31 | my %headers = @{ $args{headers} }; 32 | is $headers{'Content-Type'}, 'application/json'; 33 | 34 | my $content = decode_json($args{content}); 35 | eq_hash $content, { 36 | description => 'audienceGroupName', 37 | requestId => '12222', 38 | }; 39 | 40 | +{ 41 | audienceGroupId => 4389303728991, 42 | type => 'IMP', 43 | description => 'test', 44 | created => 1500351844, 45 | requestId => 'f70dd685-499a-4231-a441-f24b8d4fba21', 46 | } 47 | } 48 | }; 49 | 50 | done_testing(); 51 | -------------------------------------------------------------------------------- /t/16_create_audience_for_uploading.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#create_audience_for_uploading' => sub { 17 | subtest 'only required paraemter' => sub { 18 | send_request { 19 | my $res = $bot->create_audience_for_uploading({ 20 | description => 'sample text', 21 | isIfaAudience => JSON::XS::false, 22 | }); 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'POST'; 28 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/upload'; 29 | 30 | my %headers = @{ $args{headers} }; 31 | is $headers{'Content-Type'}, 'application/json'; 32 | 33 | my $content = decode_json($args{content} // ''); 34 | is $content->{description}, 'sample text'; 35 | ok !$content->{isIfaAudience}; 36 | 37 | +{} 38 | }; 39 | }; 40 | 41 | subtest 'full parameter' => sub { 42 | send_request { 43 | my $res = $bot->create_audience_for_uploading({ 44 | description => 'sample text', 45 | isIfaAudience => JSON::XS::true, 46 | uploadDescription => 'sample text', 47 | audiences => [ 48 | { 49 | id => 123, 50 | }, 51 | { 52 | id => 124, 53 | }, 54 | ], 55 | }); 56 | ok $res->is_success; 57 | is $res->http_status, 200; 58 | } receive_request { 59 | my %args = @_; 60 | is $args{method}, 'POST'; 61 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/upload'; 62 | 63 | my %headers = @{ $args{headers} }; 64 | is $headers{'Content-Type'}, 'application/json'; 65 | 66 | my $content = decode_json($args{content} // ''); 67 | is $content->{description}, 'sample text'; 68 | ok $content->{isIfaAudience}; 69 | is $content->{uploadDescription}, 'sample text'; 70 | is @{ $content->{audiences} }, 2; 71 | is $content->{audiences}->[0]->{id}, 123; 72 | is $content->{audiences}->[1]->{id}, 124; 73 | 74 | +{} 75 | }; 76 | }; 77 | }; 78 | 79 | done_testing(); 80 | -------------------------------------------------------------------------------- /t/16_delete_audience.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::Audience->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#delete_audience' => sub { 16 | my $content_type = 'application/json'; 17 | 18 | send_request { 19 | my $res = $bot->delete_audience({ audienceGroupId => 12345678 }); 20 | ok $res->is_success; 21 | is $res->http_status, 200; 22 | } receive_request { 23 | my %args = @_; 24 | is $args{method}, 'DELETE'; 25 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/12345678'; 26 | 27 | +{} 28 | } 29 | }; 30 | 31 | done_testing(); 32 | -------------------------------------------------------------------------------- /t/16_get_audience_data.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#get_audience_data' => sub { 17 | my $content_type = 'application/json'; 18 | 19 | send_request { 20 | my $res = $bot->get_audience_data({ 21 | audienceGroupId => 12345678, 22 | }); 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | 26 | is $res->audience_group_id, '12345678'; 27 | is $res->audienceGroupId, '12345678'; 28 | is $res->type, 'UPLOAD'; 29 | is $res->description, 'sample text'; 30 | is $res->status, 'READY'; 31 | is $res->audience_count, 100; 32 | is $res->audienceCount, 100; 33 | is $res->created, 1587727997; 34 | ok $res->is_ifa_audience; 35 | ok $res->isIfaAudience; 36 | is $res->permission, 'READ'; 37 | is $res->create_route, 'MESSAGING_API'; 38 | is $res->createRoute, 'MESSAGING_API'; 39 | my @jobs = $res->jobs; 40 | is $jobs[0]->{audienceGroupJobId}, 12345; 41 | is $jobs[0]->{audienceGroupId}, 12345678; 42 | is $jobs[0]->{description}, 'sample text'; 43 | is $jobs[0]->{type}, 'DIFF_ADD'; 44 | is $jobs[0]->{jobStatus}, 'WORKING'; 45 | is $jobs[0]->{audienceCount}, 123; 46 | is $jobs[0]->{created}, 1587727997; 47 | } receive_request { 48 | my %args = @_; 49 | is $args{method}, 'GET'; 50 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/12345678'; 51 | 52 | +{ 53 | audienceGroupId => '12345678', 54 | type => 'UPLOAD', 55 | description => 'sample text', 56 | status => 'READY', 57 | audienceCount => 100, 58 | created => 1587727997, 59 | isIfaAudience => JSON::XS::true, 60 | permission => 'READ', 61 | createRoute => 'MESSAGING_API', 62 | jobs => ({ 63 | audienceGroupJobId => 12345, 64 | audienceGroupId => 12345678, 65 | description => 'sample text', 66 | type => 'DIFF_ADD', 67 | jobStatus => 'WORKING', 68 | audienceCount => 123, 69 | created => 1587727997, 70 | }), 71 | } 72 | } 73 | }; 74 | 75 | done_testing(); 76 | -------------------------------------------------------------------------------- /t/16_get_authority_level.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::Audience->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#get_authority_level' => sub { 16 | my $content_type = 'application/json'; 17 | 18 | send_request { 19 | my $res = $bot->get_authority_level(); 20 | ok $res->is_success; 21 | is $res->http_status, 200; 22 | 23 | is $res->authority_level, 'sample_level_text'; 24 | is $res->authorityLevel, 'sample_level_text'; 25 | 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'GET'; 29 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/authorityLevel'; 30 | 31 | +{ 32 | authorityLevel => 'sample_level_text', 33 | } 34 | } 35 | }; 36 | 37 | done_testing(); 38 | -------------------------------------------------------------------------------- /t/16_get_data_for_multiple_audience.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#get_data_for_multiple_audience' => sub { 17 | subtest 'only required parameter' => sub { 18 | send_request { 19 | my $res = $bot->get_data_for_multiple_audience({ 20 | page => 1 21 | }); 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | 25 | my @groups = $res->audience_groups; 26 | 27 | is $groups[0]->{audienceGroupId}, '123'; 28 | is $groups[0]->{type}, 'UPLOAD'; 29 | is $groups[0]->{description}, 'sample'; 30 | is $groups[0]->{status}, 'READY'; 31 | is $groups[0]->{audienceCount}, 100; 32 | is $groups[0]->{created}, 1587757389; 33 | ok $groups[0]->{isIfaAudience}; 34 | is $groups[0]->{permission}, 'READ'; 35 | is $groups[0]->{createRoute}, 'MESSAGING_API'; 36 | 37 | is $res->totalCount, 1; 38 | is $res->readWriteAudienceGroupTotalCount, 0; 39 | is $res->page, 1; 40 | is $res->size, 1; 41 | 42 | } receive_request { 43 | my %args = @_; 44 | is $args{method}, 'GET'; 45 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/list?' 46 | . 'page=1' 47 | . '&description=' 48 | . '&status=' 49 | . '&size=20' 50 | . '&includesExternalPublicGroups=' 51 | . '&createRoute='; 52 | 53 | +{ 54 | audienceGroups => ( 55 | { 56 | audienceGroupId => '123', 57 | type => 'UPLOAD', 58 | description => 'sample', 59 | status => 'READY', 60 | audienceCount => 100, 61 | created => 1587757389, 62 | isIfaAudience => JSON::XS::true, 63 | permission => 'READ', 64 | createRoute => 'MESSAGING_API', 65 | }, 66 | ), 67 | totalCount => 1, 68 | readWriteAudienceGroupTotalCount => 0, 69 | page => 1, 70 | size => 1, 71 | } 72 | }; 73 | }; 74 | 75 | subtest 'full parameter' => sub { 76 | send_request { 77 | my $res = $bot->get_data_for_multiple_audience({ 78 | page => 1, 79 | description => 'sample', 80 | status => 'READY', 81 | size => 20, 82 | includesExternalPublicGroups => 'true', 83 | createRoute => 'MESSAGING_API', 84 | }); 85 | ok $res->is_success; 86 | is $res->http_status, 200; 87 | 88 | my @groups = $res->audience_groups; 89 | 90 | is $groups[0]->{audienceGroupId}, '123'; 91 | is $groups[0]->{type}, 'UPLOAD'; 92 | is $groups[0]->{description}, 'sample'; 93 | is $groups[0]->{status}, 'READY'; 94 | is $groups[0]->{audienceCount}, 100; 95 | is $groups[0]->{created}, 1587757389; 96 | ok $groups[0]->{isIfaAudience}; 97 | is $groups[0]->{permission}, 'READ'; 98 | is $groups[0]->{createRoute}, 'MESSAGING_API'; 99 | 100 | is $res->totalCount, 1; 101 | is $res->readWriteAudienceGroupTotalCount, 0; 102 | is $res->page, 1; 103 | is $res->size, 1; 104 | 105 | } receive_request { 106 | my %args = @_; 107 | is $args{method}, 'GET'; 108 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/list?' 109 | . 'page=1' 110 | . '&description=sample' 111 | . '&status=READY' 112 | . '&size=20' 113 | . '&includesExternalPublicGroups=true' 114 | . '&createRoute=MESSAGING_API'; 115 | 116 | +{ 117 | audienceGroups => ( 118 | { 119 | audienceGroupId => '123', 120 | type => 'UPLOAD', 121 | description => 'sample', 122 | status => 'READY', 123 | audienceCount => 100, 124 | created => 1587757389, 125 | isIfaAudience => JSON::XS::true, 126 | permission => 'READ', 127 | createRoute => 'MESSAGING_API', 128 | }, 129 | ), 130 | totalCount => 1, 131 | readWriteAudienceGroupTotalCount => 0, 132 | page => 1, 133 | size => 1, 134 | } 135 | }; 136 | }; 137 | 138 | }; 139 | 140 | done_testing(); 141 | -------------------------------------------------------------------------------- /t/16_rename_audience.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#rename_audience' => sub { 17 | my $content_type = 'application/json'; 18 | 19 | send_request { 20 | my $res = $bot->rename_audience({ 21 | audience_group_id => 12345678, 22 | description => 'audienceGroupName', 23 | }); 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/12345678/updateDescription'; 30 | 31 | my %headers = @{ $args{headers} }; 32 | is $headers{'Content-Type'}, 'application/json'; 33 | 34 | my $content = decode_json($args{content}); 35 | eq_hash $content, { 36 | description => 'audienceGroupName', 37 | }; 38 | 39 | +{} 40 | } 41 | }; 42 | 43 | done_testing(); 44 | -------------------------------------------------------------------------------- /t/16_update_authority_level.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Audience; 8 | use Furl; 9 | use JSON::XS; 10 | 11 | my $bot = LINE::Bot::Audience->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest '#update_authority_level' => sub { 17 | my $content_type = 'application/json'; 18 | 19 | send_request { 20 | my $res = $bot->update_authority_level({ 21 | authorityLevel => 'PUBLIC', 22 | }); 23 | ok $res->is_success; 24 | is $res->http_status, 200; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'PUT'; 28 | is $args{url}, 'https://api.line.me/v2/bot/audienceGroup/authorityLevel'; 29 | 30 | my %headers = @{ $args{headers} }; 31 | is $headers{'Content-Type'}, 'application/json'; 32 | 33 | my $content = decode_json($args{content} // ''); 34 | is $content->{authorityLevel}, 'PUBLIC'; 35 | 36 | +{} 37 | } 38 | }; 39 | 40 | done_testing(); 41 | -------------------------------------------------------------------------------- /t/17_get_narrowcast_message_status.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::Message::Narrowcast; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::Message::Narrowcast->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#get_narrowcast_message_status' => sub { 16 | my $content_type = 'application/json'; 17 | 18 | send_request { 19 | my $res = $bot->get_narrowcast_message_status(12345); 20 | 21 | ok $res->is_success; 22 | is $res->http_status, 200; 23 | is $res->phase, 'waiting'; 24 | is $res->successCount, 100; 25 | is $res->success_count, 100; # alias 26 | is $res->failureCount, 0; 27 | is $res->failure_count, 0; # alias 28 | is $res->targetCount, 100; 29 | is $res->target_count, 100; # alias 30 | is $res->failedDescription, 'sample description'; 31 | is $res->failed_description, 'sample description'; # alias 32 | is $res->errorCode, 1; 33 | is $res->error_code, 1; 34 | is $res->acceptedTime, '2021-01-01T00:00:00.000Z'; 35 | is $res->accepted_time, '2021-01-01T00:00:00.000Z'; # alias 36 | is $res->completedTime, '2021-01-01T01:23:45.678Z'; 37 | is $res->completed_time, '2021-01-01T01:23:45.678Z'; # alias 38 | } receive_request { 39 | my %args = @_; 40 | is $args{method}, 'GET'; 41 | is $args{url}, 'https://api.line.me/v2/bot/message/progress/narrowcast?requestId=12345'; 42 | 43 | +{ 44 | phase => 'waiting', 45 | successCount => 100, 46 | failureCount => 0, 47 | targetCount => 100, 48 | failedDescription => 'sample description', 49 | errorCode => 1, 50 | acceptedTime => '2021-01-01T00:00:00.000Z', 51 | completedTime => '2021-01-01T01:23:45.678Z' 52 | } 53 | } 54 | }; 55 | 56 | done_testing(); 57 | -------------------------------------------------------------------------------- /t/21_get_profile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | 9 | my $bot = LINE::Bot::API->new( 10 | channel_secret => 'testsecret', 11 | channel_access_token => 'ACCESS_TOKEN', 12 | ); 13 | send_request { 14 | my $res = $bot->get_profile( 15 | 'USER_ID', 16 | ); 17 | isa_ok $res, 'LINE::Bot::API::Response::Profile'; 18 | ok $res->is_success; 19 | is $res->http_status, 200; 20 | 21 | is $res->display_name, 'Messaging API'; 22 | is $res->user_id, 'userId'; 23 | is $res->picture_url, 'http://example.com/abcdefghijklmn'; 24 | is $res->status_message, 'Hello, LINE!'; 25 | is $res->language, 'en'; 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'GET'; 29 | is $args{url}, 'https://api.line.me/v2/bot/profile/USER_ID'; 30 | 31 | +{ 32 | displayName => 'Messaging API', 33 | userId => 'userId', 34 | pictureUrl => 'http://example.com/abcdefghijklmn', 35 | statusMessage => 'Hello, LINE!', 36 | language => 'en', 37 | }; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/22_get_group_member_profile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | 9 | my $bot = LINE::Bot::API->new( 10 | channel_secret => 'testsecret', 11 | channel_access_token => 'ACCESS_TOKEN', 12 | ); 13 | send_request { 14 | my $res = $bot->get_group_member_profile( 15 | 'GROUP_ID', 16 | 'USER_ID', 17 | ); 18 | isa_ok $res, 'LINE::Bot::API::Response::GroupMemberProfile'; 19 | ok $res->is_success; 20 | is $res->http_status, 200; 21 | 22 | is $res->display_name, 'Messaging API'; 23 | is $res->user_id, 'userId'; 24 | is $res->picture_url, 'http://example.com/abcdefghijklmn'; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'GET'; 28 | is $args{url}, 'https://api.line.me/v2/bot/group/GROUP_ID/member/USER_ID'; 29 | 30 | +{ 31 | displayName => 'Messaging API', 32 | userId => 'userId', 33 | pictureUrl => 'http://example.com/abcdefghijklmn', 34 | }; 35 | }; 36 | 37 | done_testing; 38 | 39 | -------------------------------------------------------------------------------- /t/22_get_group_summary.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#get_group_summary' => sub { 16 | send_request { 17 | my $res = $bot->get_group_summary('1234567890'); 18 | ok $res->is_success; 19 | is $res->http_status, 200; 20 | 21 | is $res->groupId, 1234567890; 22 | is $res->groupName, 'test_group_name'; 23 | is $res->pictureUrl, 'https://example.com/dummy_url'; 24 | 25 | # alias 26 | is $res->group_id, 1234567890; 27 | is $res->group_name, 'test_group_name'; 28 | is $res->pictureUrl, 'https://example.com/dummy_url'; 29 | } receive_request { 30 | my %args = @_; 31 | 32 | is $args{method}, 'GET'; 33 | is $args{url}, 'https://api.line.me/v2/bot/group/1234567890/summary'; 34 | 35 | + { 36 | groupId => 1234567890, 37 | groupName => 'test_group_name', 38 | pictureUrl => 'https://example.com/dummy_url', 39 | } 40 | }; 41 | }; 42 | 43 | done_testing(); 44 | -------------------------------------------------------------------------------- /t/22_get_member_in_group_count.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#get_member_in_group_count' => sub { 16 | send_request { 17 | my $res = $bot->get_member_in_group_count('1234567890'); 18 | ok $res->is_success; 19 | is $res->http_status, 200; 20 | 21 | is $res->count, 100; 22 | 23 | } receive_request { 24 | my %args = @_; 25 | 26 | is $args{method}, 'GET'; 27 | is $args{url}, 'https://api.line.me/v2/bot/group/1234567890/members/count'; 28 | 29 | + { 30 | count => 100, 31 | } 32 | }; 33 | }; 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/22_get_member_in_room_count.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest '#get_member_in_room_count' => sub { 16 | send_request { 17 | my $res = $bot->get_member_in_room_count('1234567890'); 18 | ok $res->is_success; 19 | is $res->http_status, 200; 20 | 21 | is $res->count, 100; 22 | 23 | } receive_request { 24 | my %args = @_; 25 | 26 | is $args{method}, 'GET'; 27 | is $args{url}, 'https://api.line.me/v2/bot/room/1234567890/members/count'; 28 | 29 | + { 30 | count => 100, 31 | } 32 | }; 33 | }; 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/22_get_room_member_profile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | 9 | my $bot = LINE::Bot::API->new( 10 | channel_secret => 'testsecret', 11 | channel_access_token => 'ACCESS_TOKEN', 12 | ); 13 | send_request { 14 | my $res = $bot->get_room_member_profile( 15 | 'ROOM_ID', 16 | 'USER_ID', 17 | ); 18 | isa_ok $res, 'LINE::Bot::API::Response::RoomMemberProfile'; 19 | ok $res->is_success; 20 | is $res->http_status, 200; 21 | 22 | is $res->display_name, 'Messaging API'; 23 | is $res->user_id, 'userId'; 24 | is $res->picture_url, 'http://example.com/abcdefghijklmn'; 25 | } receive_request { 26 | my %args = @_; 27 | is $args{method}, 'GET'; 28 | is $args{url}, 'https://api.line.me/v2/bot/room/ROOM_ID/member/USER_ID'; 29 | 30 | +{ 31 | displayName => 'Messaging API', 32 | userId => 'userId', 33 | pictureUrl => 'http://example.com/abcdefghijklmn', 34 | }; 35 | }; 36 | 37 | done_testing; 38 | 39 | -------------------------------------------------------------------------------- /t/42_create_events_by_api.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | 9 | my $config = +{ 10 | channel_secret => 'testsecret', 11 | channel_access_token => 'ACCESS_TOKEN', 12 | }; 13 | 14 | my $json = <new(%{ $config }); 230 | 231 | subtest 'validate_signature' => sub { 232 | subtest 'failed' => sub { 233 | ok(! $bot->validate_signature($json, '')); 234 | }; 235 | 236 | subtest 'successful' => sub { 237 | ok($bot->validate_signature($json, 'eicPGyl5tJ17UNzckv1+0P4QJ9xKCG0UEri6mzfOiCs=')); 238 | }; 239 | }; 240 | 241 | subtest 'parse_events_from_json' => sub { 242 | my $events = $bot->parse_events_from_json($json); 243 | is scalar(@{ $events }), 16; 244 | }; 245 | 246 | done_testing; 247 | -------------------------------------------------------------------------------- /t/51_issue_link_token.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use LINE::Bot::API::Builder::SendMessage; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | send_request { 16 | my $res = $bot->issue_link_token('DUMMY_ID'); 17 | ok $res->is_success; 18 | is $res->http_status, 200; 19 | } receive_request { 20 | my %args = @_; 21 | is $args{method}, 'POST'; 22 | is $args{url}, 'https://api.line.me/v2/bot/user/DUMMY_ID/linkToken'; 23 | 24 | my $has_header = 0; 25 | my @headers = @{ $args{headers} }; 26 | while (my($key, $value) = splice @headers, 0, 2) { 27 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 28 | } 29 | is $has_header, 1; 30 | 31 | +{}; 32 | }; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/52_download_rich_menu_image.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use Furl; 9 | 10 | my $bot = LINE::Bot::API->new( 11 | channel_secret => 'testsecret', 12 | channel_access_token => 'ACCESS_TOKEN', 13 | ); 14 | 15 | subtest download_rich_menu_image => sub { 16 | send_get_content_request { 17 | my $res = $bot->download_rich_menu_image('DUMMY_RICH_MENU_ID'); 18 | is $res, 'image binary'; 19 | } receive_request { 20 | my %args = @_; 21 | is $args{method}, 'GET'; 22 | is $args{url}, 'https://api-data.line.me/v2/bot/richmenu/DUMMY_RICH_MENU_ID/content'; 23 | 24 | 'image binary'; 25 | } 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/52_rich_menu_response.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use JSON::XS qw(decode_json); 8 | use LINE::Bot::API::Response::RichMenu; 9 | 10 | use JSON::XS qw(decode_json); 11 | 12 | my $res_json = decode_json(q<{"richMenuId": "333fakeRichMenuID"}>); 13 | 14 | my $res = LINE::Bot::API::Response::RichMenu->new( 15 | http_status => 200, 16 | %$res_json 17 | ); 18 | 19 | is $res->rich_menu_id, '333fakeRichMenuID'; 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/52_upload_rich_menu_image.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | use Furl; 9 | use Data::Dumper 'Dumper'; 10 | 11 | my $bot = LINE::Bot::API->new( 12 | channel_secret => 'testsecret', 13 | channel_access_token => 'ACCESS_TOKEN', 14 | ); 15 | 16 | subtest upload_rich_menu_image => sub { 17 | my $contentType = 'image/jpeg'; 18 | my $imagePath = 't/controller-rich-menu-image-sample.jpg'; 19 | 20 | send_request { 21 | my $res = $bot->upload_rich_menu_image('DUMMY_RICH_MENU_ID', $contentType, $imagePath); 22 | ok $res->is_success; 23 | is $res->http_status, 200; 24 | } receive_request { 25 | my %args = @_; 26 | is $args{method}, 'POST'; 27 | is $args{url}, 'https://api-data.line.me/v2/bot/richmenu/DUMMY_RICH_MENU_ID/content'; 28 | 29 | my %headers = @{ $args{headers} }; 30 | is $headers{'Content-Type'}, 'image/jpeg'; 31 | 32 | +{}; 33 | } 34 | }; 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/54-get_bot_info.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use LINE::Bot::API; 8 | 9 | my $bot = LINE::Bot::API->new( 10 | channel_secret => 'testsecret', 11 | channel_access_token => 'ACCESS_TOKEN', 12 | ); 13 | 14 | my $fake_response = +{ 15 | "userId" => "Ub9952f8...", 16 | "basicId" => "@216ru...", 17 | "displayName" => "Example name", 18 | "pictureUrl" => "https://obs.line-apps.com/...", 19 | "chatMode" => "chat", 20 | "markAsReadMode" => "manual" 21 | }; 22 | 23 | send_request { 24 | my $res = $bot->get_bot_info(); 25 | 26 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 27 | isa_ok $res, 'LINE::Bot::API::Response::BotInfo'; 28 | 29 | ok $res->is_success; 30 | is $res->http_status, 200; 31 | 32 | for my $attr (qw(userId basicId premiumId displayName pictureUrl chatMode markAsReadMode)) { 33 | ok $res->can($attr); 34 | is $res->$attr, $fake_response->{$attr}; 35 | } 36 | 37 | } receive_request { 38 | my %args = @_; 39 | is $args{method}, 'GET'; 40 | 41 | is $args{url}, 'https://api.line.me/v2/bot/info'; 42 | 43 | $fake_response; 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/55-get-webhook-endpoint-information.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use lib 't/lib'; 3 | use t::Util; 4 | 5 | use LINE::Bot::API; 6 | 7 | my $bot = LINE::Bot::API->new( 8 | channel_secret => 'testsecret', 9 | channel_access_token => 'ACCESS_TOKEN', 10 | ); 11 | send_request { 12 | my $res = $bot->get_webhook_endpoint_information(); 13 | 14 | isa_ok $res, 'LINE::Bot::API::Response::WebhookInformation'; 15 | is $res->is_success, T(); 16 | is $res->http_status, 200; 17 | 18 | is $res->endpoint, 'https://example.com/webhook'; 19 | is $res->active, T(); 20 | } receive_request { 21 | my %args = @_; 22 | is $args{method}, 'GET'; 23 | is $args{url}, 'https://api.line.me/v2/bot/channel/webhook/endpoint'; 24 | 25 | +{ 26 | endpoint => 'https://example.com/webhook', 27 | active => 1, 28 | } 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/55-set-webhook-url.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use lib 't/lib'; 3 | use t::Util; 4 | 5 | use LINE::Bot::API; 6 | 7 | my $bot = LINE::Bot::API->new( 8 | channel_secret => 'testsecret', 9 | channel_access_token => 'ACCESS_TOKEN', 10 | ); 11 | 12 | send_request { 13 | my $webhook_url = "https://example.com/webhook/" . int(rand(100000000)); 14 | 15 | my $res = $bot->set_webhook_url({ 'endpoint' => $webhook_url }); 16 | 17 | isa_ok $res, 'LINE::Bot::API::Response::Common'; 18 | is $res->is_success, T(); 19 | is $res->http_status, 200; 20 | } receive_request { 21 | my %args = @_; 22 | is $args{method}, 'PUT'; 23 | is $args{url}, 'https://api.line.me/v2/bot/channel/webhook/endpoint'; 24 | 25 | +{}; 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/55-test-webhook.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use lib 't/lib'; 3 | use t::Util; 4 | 5 | use LINE::Bot::API; 6 | 7 | my $bot = LINE::Bot::API->new( 8 | channel_secret => 'testsecret', 9 | channel_access_token => 'ACCESS_TOKEN', 10 | ); 11 | send_request { 12 | my $webhook_url = "https://example.com/webhook/" . int(rand(100000000)); 13 | 14 | my $res = $bot->test_webhook_endpoint({ 'endpoint' => $webhook_url }); 15 | 16 | isa_ok $res, 'LINE::Bot::API::Response::WebhookTest'; 17 | is $res->is_success, T(); 18 | is $res->http_status, 200; 19 | 20 | is $res->success(), T(); 21 | is $res->timestamp(), E(); 22 | is $res->statusCode(), E(); 23 | is $res->reason(), E(); 24 | is $res->detail(), E(); 25 | 26 | } receive_request { 27 | my %args = @_; 28 | is $args{method}, 'POST'; 29 | is $args{url}, 'https://api.line.me/v2/bot/channel/webhook/endpoint'; 30 | 31 | +{ 32 | success => 1, 33 | timestamp => time() * 1000, 34 | statusCode => 200, 35 | reason => 'OK', 36 | detail => '...', 37 | } 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/56_get_followers.t: -------------------------------------------------------------------------------- 1 | use Test2::V0; 2 | use lib 't/lib'; 3 | use t::Util; 4 | 5 | use LINE::Bot::API; 6 | 7 | my $bot = LINE::Bot::API->new( 8 | channel_secret => 'testsecret', 9 | channel_access_token => 'ACCESS_TOKEN', 10 | ); 11 | 12 | subtest get_with_start_and_limit => sub { 13 | send_request { 14 | my $res = $bot->get_followers({ 'start' => 'start_token', 'limit' => 100 }); 15 | 16 | isa_ok $res, 'LINE::Bot::API::Response::Followers'; 17 | is $res->is_success, T(); 18 | is $res->http_status, 200; 19 | 20 | is $res->user_ids, [1,2,3]; 21 | is $res->next, U(); 22 | } receive_request { 23 | my %args = @_; 24 | is $args{method}, 'GET'; 25 | is $args{url}, 'https://api.line.me/v2/bot/followers/ids?limit=100&start=start_token'; 26 | 27 | +{ 28 | userIds => [1,2,3] 29 | } 30 | }; 31 | }; 32 | 33 | subtest get_with_limit => sub { 34 | send_request { 35 | my $res = $bot->get_followers({ 'limit' => 100 }); 36 | 37 | isa_ok $res, 'LINE::Bot::API::Response::Followers'; 38 | is $res->is_success, T(); 39 | is $res->http_status, 200; 40 | 41 | is $res->user_ids, [1,2,3]; 42 | is $res->next, U(); 43 | } receive_request { 44 | my %args = @_; 45 | is $args{method}, 'GET'; 46 | is $args{url}, 'https://api.line.me/v2/bot/followers/ids?limit=100'; 47 | 48 | +{ 49 | userIds => [1,2,3] 50 | } 51 | }; 52 | }; 53 | 54 | subtest get_with_start => sub { 55 | send_request { 56 | my $res = $bot->get_followers({ 'start' => 'start_token'}); 57 | 58 | isa_ok $res, 'LINE::Bot::API::Response::Followers'; 59 | is $res->is_success, T(); 60 | is $res->http_status, 200; 61 | 62 | is $res->user_ids, [1,2,3]; 63 | is $res->next, U(); 64 | } receive_request { 65 | my %args = @_; 66 | is $args{method}, 'GET'; 67 | is $args{url}, 'https://api.line.me/v2/bot/followers/ids?start=start_token'; 68 | 69 | +{ 70 | userIds => [1,2,3] 71 | } 72 | }; 73 | }; 74 | 75 | subtest get_no_parameter => sub { 76 | send_request { 77 | my $res = $bot->get_followers(); 78 | 79 | isa_ok $res, 'LINE::Bot::API::Response::Followers'; 80 | is $res->is_success, T(); 81 | is $res->http_status, 200; 82 | 83 | is $res->user_ids, [1,2,3]; 84 | is $res->next, 'next_token'; 85 | } receive_request { 86 | my %args = @_; 87 | is $args{method}, 'GET'; 88 | is $args{url}, 'https://api.line.me/v2/bot/followers/ids'; 89 | 90 | +{ 91 | userIds => [1,2,3], 92 | next => 'next_token' 93 | } 94 | }; 95 | }; 96 | 97 | 98 | done_testing; 99 | -------------------------------------------------------------------------------- /t/PR75-get-number-of-followers.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use lib 't/lib'; 6 | use t::Util; 7 | 8 | use LINE::Bot::API; 9 | use LINE::Bot::API::Builder::SendMessage; 10 | use Carp (); 11 | 12 | $SIG{__DIE__} = \&Carp::confess; 13 | 14 | my $bot = LINE::Bot::API->new( 15 | channel_secret => 'testsecret', 16 | channel_access_token => 'ACCESS_TOKEN', 17 | ); 18 | 19 | send_request { 20 | my $res = $bot->get_number_of_followers({ date => "20200214" }); 21 | ok $res->is_success; 22 | is $res->http_status, 200; 23 | is $res->status, "ready"; 24 | is $res->followers, 42; 25 | is $res->targetedReaches, 12345; 26 | is $res->blocks, 4321; 27 | } receive_request { 28 | my %args = @_; 29 | is $args{method}, 'GET'; 30 | is $args{url}, 'https://api.line.me/v2/bot/insight/followers?date=20200214', 31 | 32 | my $has_header = 0; 33 | my @headers = @{ $args{headers} }; 34 | while (my($key, $value) = splice @headers, 0, 2) { 35 | $has_header++ if $key eq 'Authorization' && $value eq 'Bearer ACCESS_TOKEN'; 36 | } 37 | is $has_header, 1; 38 | 39 | +{ 40 | status => "ready", 41 | followers => 42, 42 | targetedReaches => 12345, 43 | blocks => 4321, 44 | }; 45 | }; 46 | 47 | done_testing; 48 | -------------------------------------------------------------------------------- /t/controller-rich-menu-image-sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/line/line-bot-sdk-perl/adf698c971845e5cab6fa49f4b8a4d75984a9e1b/t/controller-rich-menu-image-sample.jpg -------------------------------------------------------------------------------- /t/examples/account-link-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "accountLink", 3 | "replyToken": "b60d432864f44d079f6d8efe86cf404b", 4 | "source": { 5 | "userId": "U91eeaf62d...", 6 | "type": "user" 7 | }, 8 | "timestamp": 1513669370317, 9 | "link": { 10 | "result": "ok", 11 | "nonce": "xxxxxxxxxxxxxxx" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/examples/audio-message-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "message", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | }, 9 | "message": { 10 | "id": "325708", 11 | "type": "audio", 12 | "duration": 60000, 13 | "contentProvider": { 14 | "type": "line" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /t/examples/beacon-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "beacon", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | }, 9 | "beacon": { 10 | "hwid": "d41d8cd98f", 11 | "type": "enter" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/examples/device-link-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "things", 3 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U91eeaf62d..." 8 | }, 9 | "things": { 10 | "deviceId": "t2c449c9d1...", 11 | "type": "link" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/examples/device-unlink-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "things", 3 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U91eeaf62d..." 8 | }, 9 | "things": { 10 | "deviceId": "t2c449c9d1...", 11 | "type": "unlink" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /t/examples/error-response-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "message":"The request body has 2 error(s)", 3 | "details":[ 4 | { 5 | "message":"May not be empty", 6 | "property":"messages[0].text" 7 | }, 8 | { 9 | "message":"Must be one of the following values: [text, image, video, audio, location, sticker, template, imagemap]", 10 | "property":"messages[1].type" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /t/examples/follow-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "follow", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /t/examples/image-message-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "message", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | }, 9 | "message": { 10 | "id": "325708", 11 | "type": "image", 12 | "contentProvider": { 13 | "type": "line" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /t/examples/join-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "join", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "group", 7 | "groupId": "C4af4980629..." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /t/examples/leave-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "leave", 3 | "timestamp": 1462629479859, 4 | "source": { 5 | "type": "group", 6 | "groupId": "C4af4980629..." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /t/examples/member-joined-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "0f3779fba3b349968c5d07db31eabf65", 3 | "type": "memberJoined", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "group", 7 | "groupId": "C4af4980629..." 8 | }, 9 | "joined": { 10 | "members": [ 11 | { 12 | "type": "user", 13 | "userId": "U4af4980629..." 14 | }, 15 | { 16 | "type": "user", 17 | "userId": "U91eeaf62d9..." 18 | } 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /t/examples/member-left-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "memberLeft", 3 | "timestamp": 1462629479960, 4 | "source": { 5 | "type": "group", 6 | "groupId": "C4af4980629..." 7 | }, 8 | "left": { 9 | "members": [ 10 | { 11 | "type": "user", 12 | "userId": "U4af4980629..." 13 | }, 14 | { 15 | "type": "user", 16 | "userId": "U91eeaf62d9..." 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /t/examples/postback-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"postback", 3 | "replyToken":"b60d432864f44d079f6d8efe86cf404b", 4 | "source":{ 5 | "userId":"U91eeaf62d...", 6 | "type":"user" 7 | }, 8 | "timestamp":1513669370317, 9 | "postback":{ 10 | "data":"storeId=12345", 11 | "params":{ 12 | "datetime":"2017-12-25T01:00" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /t/examples/text-message-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "message", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | }, 9 | "message": { 10 | "id": "325708", 11 | "type": "text", 12 | "text": "Hello, world!" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /t/examples/unfollow-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "unfollow", 3 | "timestamp": 1462629479859, 4 | "source": { 5 | "type": "user", 6 | "userId": "U4af4980629..." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /t/examples/unsend-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "unsend", 3 | "timestamp": 1462629479859, 4 | "source": { 5 | "type": "group", 6 | "groupId": "C4af4980629..." 7 | }, 8 | "unsend": { 9 | "messageId": "325708" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /t/examples/video-message-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "message", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "user", 7 | "userId": "U4af4980629..." 8 | }, 9 | "message": { 10 | "id": "325708", 11 | "type": "video", 12 | "duration": 60000, 13 | "contentProvider": { 14 | "type": "external", 15 | "originalContentUrl": "https://example.com/original.mp4", 16 | "previewImageUrl": "https://example.com/preview.jpg" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /t/examples/video-viewing-complete-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA", 3 | "type": "videoPlayComplete", 4 | "timestamp": 1462629479859, 5 | "source": { 6 | "type": "group", 7 | "groupId": "C4af4980629..." 8 | }, 9 | "videoPlayComplete": { 10 | "trackingId": "track-id" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /t/lib/t/Util.pm: -------------------------------------------------------------------------------- 1 | package t::Util; 2 | use strict; 3 | use warnings; 4 | use parent 'Exporter'; 5 | 6 | our @EXPORT = qw( 7 | send_request send_get_content_request receive_request 8 | ); 9 | 10 | use JSON::XS; 11 | use Furl::HTTP; 12 | use Furl::Response; 13 | 14 | sub send_request (&@) { 15 | my($code, $mock) = @_; 16 | no warnings 'redefine'; 17 | local *Furl::HTTP::request = sub { 18 | shift; 19 | my @ret = $mock->(@_); 20 | 21 | my ($http_status, $headers, $body); 22 | if (@ret == 1) { 23 | $body = $ret[0]; 24 | } elsif (@ret == 2) { 25 | ($http_status, $body) = @ret; 26 | } elsif (@ret == 3) { 27 | ($http_status, $headers, $body) = @ret; 28 | } 29 | 30 | $http_status //= delete $body->{http_status} // 200; 31 | $headers //= []; 32 | 33 | my $json = encode_json $body; 34 | return ('0', $http_status, 'OK', [ 35 | @$headers, 36 | 'X-Line-Request-Id' => 'dummy_id', 37 | 'Content-Type' => 'application/json; charset=UTF-8', 38 | 'Content-Length' => length($json), 39 | ], $json); 40 | }; 41 | $code->(); 42 | } 43 | sub send_get_content_request (&@) { 44 | my($code, $mock) = @_; 45 | no warnings 'redefine'; 46 | local *Furl::HTTP::request = sub { 47 | shift; 48 | my $ret = $mock->(@_); 49 | my $http_status = 200; 50 | return ('0', $http_status, 'OK', [ 51 | 'X-Line-Request-Id' => 'dummy_id', 52 | 'Content-Type' => 'image/jpeg', 53 | 'Content-Length' => length($ret), 54 | ], $ret); 55 | }; 56 | $code->(); 57 | } 58 | sub receive_request (&) { shift } 59 | 60 | 1; 61 | -------------------------------------------------------------------------------- /t/oauth.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use lib 't/lib'; 5 | use t::Util; 6 | 7 | use URI::Escape; 8 | 9 | use LINE::Bot::API; 10 | 11 | 12 | my $bot = LINE::Bot::API->new( 13 | channel_secret => 'testsecret', 14 | channel_access_token => 'ACCESS_TOKEN', 15 | ); 16 | 17 | subtest 'issue channel access token (successful case)', sub { 18 | send_request { 19 | my $res = $bot->issue_channel_access_token({ 20 | client_id => 'DUMMY_CLIENT_ID', 21 | client_secret => $bot->{channel_secret}, 22 | }); 23 | 24 | ok $res->is_success; 25 | is $res->http_status, 200; 26 | 27 | is $res->access_token, 'NEWTOKEN'; 28 | is $res->expires_in, '2592000'; 29 | is $res->token_type, 'Bearer'; 30 | } receive_request { 31 | my %args = @_; 32 | is $args{method}, 'POST'; 33 | is $args{url}, 'https://api.line.me/v2/oauth/accessToken'; 34 | 35 | return +{ 36 | access_token => 'NEWTOKEN', 37 | expires_in => 2592000, 38 | token_type => 'Bearer', 39 | }; 40 | }; 41 | }; 42 | 43 | subtest 'renew access token (successful case)', sub { 44 | send_request { 45 | my $res = $bot->revoke_channel_access_token({ 46 | access_token => 'TOBEDELETEDTOKEN' 47 | }); 48 | 49 | ok $res->is_success; 50 | is $res->http_status, 200; 51 | } receive_request { 52 | my %args = @_; 53 | is $args{method}, 'POST'; 54 | is $args{url}, 'https://api.line.me/v2/oauth/revoke'; 55 | 56 | return +{} 57 | }; 58 | }; 59 | 60 | subtest 'renew access token (error case)', sub { 61 | send_request { 62 | my $res = $bot->revoke_channel_access_token({ 63 | access_token => 'INVALIDTOKEN' 64 | }); 65 | 66 | ok ! $res->is_success; 67 | isnt $res->http_status, 200; 68 | ok $res->error; 69 | ok $res->error_description; 70 | } receive_request { 71 | my %args = @_; 72 | is $args{method}, 'POST'; 73 | is $args{url}, 'https://api.line.me/v2/oauth/revoke'; 74 | 75 | return +{ 76 | http_status => 400, 77 | error => "ERR", 78 | error_description => "ERRRR", 79 | } 80 | }; 81 | }; 82 | 83 | subtest 'issue channel access token v2.1 (successful case)' => sub { 84 | send_request { 85 | my $res = $bot->issue_channel_access_token_v2_1({ jwt => 'DUMMY_JWT' }); 86 | 87 | ok $res->is_success; 88 | is $res->http_status, 200; 89 | 90 | is $res->access_token, 'NEWTOKEN'; 91 | is $res->expires_in, '2592000'; 92 | is $res->token_type, 'Bearer'; 93 | is $res->key_id, 'dummy_key_id' 94 | } receive_request { 95 | my %args = @_; 96 | is $args{method}, 'POST'; 97 | is $args{url}, 'https://api.line.me/oauth2/v2.1/token'; 98 | 99 | my @contents = @{$args{content} // ''}; 100 | my $has_header = 0; 101 | while (my($key, $value) = splice @contents, 0, 2) { 102 | $has_header++ if $key eq 'grant_type' && $value eq 'client_credentials'; 103 | $has_header++ if $key eq 'client_assertion_type' && $value eq 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'; 104 | $has_header++ if $key eq 'client_assertion' && $value eq 'DUMMY_JWT'; 105 | } 106 | is $has_header, 3; 107 | 108 | return +{ 109 | access_token => 'NEWTOKEN', 110 | expires_in => 2592000, 111 | token_type => 'Bearer', 112 | key_id => 'dummy_key_id', 113 | }; 114 | }; 115 | }; 116 | 117 | subtest 'get valid channel access token v2.1' => sub { 118 | 119 | my $dummy_result = [ 120 | "U_gdnFYKTWRxxxxDVZexGg", 121 | "sDTOzw5wIfWxxxxzcmeQA", 122 | "73hDyp3PxGfxxxxD6U5qYA", 123 | "FHGanaP79smDxxxxyPrVw", 124 | "CguB-0kxxxxdSM3A5Q_UtQ", 125 | "G82YP96jhHwyKSxxxx7IFA" 126 | ]; 127 | 128 | send_request { 129 | my $res = $bot->get_valid_channel_access_token_v2_1({ jwt => 'DUMMY_JWT' }); 130 | 131 | ok $res->is_success; 132 | is $res->http_status, 200; 133 | 134 | is_deeply($res->key_ids, $dummy_result); 135 | 136 | } receive_request { 137 | my %args = @_; 138 | is $args{method}, 'GET'; 139 | 140 | my $jwt = uri_escape('DUMMY_JWT'); 141 | my $assertion_type = uri_escape('urn:ietf:params:oauth:client-assertion-type:jwt-bearer'); 142 | is $args{url}, 'https://api.line.me/oauth2/v2.1/tokens/kid' . "?client_assertion_type=$assertion_type&client_assertion=$jwt"; 143 | 144 | return +{ 145 | key_ids => [ 146 | "U_gdnFYKTWRxxxxDVZexGg", 147 | "sDTOzw5wIfWxxxxzcmeQA", 148 | "73hDyp3PxGfxxxxD6U5qYA", 149 | "FHGanaP79smDxxxxyPrVw", 150 | "CguB-0kxxxxdSM3A5Q_UtQ", 151 | "G82YP96jhHwyKSxxxx7IFA" 152 | ] 153 | }; 154 | }; 155 | }; 156 | 157 | done_testing; 158 | -------------------------------------------------------------------------------- /t/types-validate-examples.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | 4 | use FindBin '$Bin'; 5 | use File::Spec; 6 | use JSON::XS qw(decode_json); 7 | use Test::More; 8 | 9 | use LINE::Bot::API::Types qw; 10 | 11 | sub verify { 12 | my ($type, $file) = @_; 13 | 14 | $file = File::Spec->join($Bin, 'examples', $file); 15 | 16 | open my $fh, '<', $file; 17 | my $c = do { local $/; <$fh> }; 18 | close($fh); 19 | 20 | eval { 21 | my $val = decode_json($c); 22 | 23 | my $error = $type->validate($val); 24 | if ($error) { 25 | fail "$type: $file"; 26 | diag $error; 27 | } else { 28 | pass "$type: $file"; 29 | } 30 | 1; 31 | } or do { 32 | my $err = $_; 33 | fail "ERROR: $type: $file"; 34 | diag $c; 35 | }; 36 | } 37 | 38 | my @tests = ( 39 | [ DeviceUnlinkEvent, 'device-unlink-event.json'], 40 | [ DeviceLinkEvent, 'device-link-event.json'], 41 | [ AccountLinkEvent, 'account-link-event.json'], 42 | [ BeaconEvent, 'beacon-event.json'], 43 | [ PostbackEvent, 'postback-event.json'], 44 | [ MemberJoinedEvent, 'member-joined-event.json'], 45 | [ MemberLeftEvent, 'member-left-event.json'], 46 | [ LeaveEvent, 'leave-event.json'], 47 | [ JoinEvent, 'join-event.json'], 48 | [ UnfollowEvent, 'unfollow-event.json'], 49 | [ FollowEvent, 'follow-event.json'], 50 | [ ErrorResponse, 'error-response-1.json'], 51 | [ MessageEvent, 'text-message-1.json'], 52 | [ MessageEvent, 'image-message-1.json'], 53 | [ MessageEvent, 'video-message-1.json'], 54 | [ MessageEvent, 'audio-message-1.json'], 55 | [ UnsendEvent, 'unsend-event.json'], 56 | [ VideoViewingCompleteEvent, 'video-viewing-complete-event.json'], 57 | ); 58 | 59 | for (@tests){ 60 | verify(@$_); 61 | } 62 | done_testing; 63 | --------------------------------------------------------------------------------