├── .circleci └── config.yml ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── linter.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml └── main ├── .crontab_exam.swp ├── Dockerfile ├── crontab ├── env └── exam.env ├── images ├── .DS_Store ├── erai_w_newtext.png ├── icon.jpg ├── old │ ├── halloween_mark_majo.png │ ├── halloween_pumpkin1.png │ ├── hometamon-remove.png │ ├── hometamon.jpg │ ├── hometamon1.jpg │ ├── hometamon_haloween2.png │ ├── hometamon_syougatu.png │ ├── metamon.jpeg │ └── metamon.png ├── otukare_w_newtext.png ├── oyasumi_w_newtext.png ├── sugoi_w_newtext.png └── yosi_w_newtext.png ├── src ├── __init__.py ├── delete_friendship_byhand.py ├── hometamon.py ├── join_images.py ├── make_icon.py ├── meta_manuscript.py └── tweet_intent.py └── tests ├── __init__.py ├── __pycache__ ├── __init__.cpython-36.pyc ├── __init__.cpython-38.pyc ├── test_access_django.cpython-36-pytest-6.0.1.pyc ├── test_hometamon.cpython-36-pytest-5.4.1.pyc ├── test_hometamon.cpython-36-pytest-6.0.1.pyc ├── test_hometamon.cpython-38-pytest-5.4.1.pyc ├── test_hometask.cpython-36-pytest-6.0.1.pyc ├── test_maketweet_intent.cpython-36-pytest-6.0.1.pyc └── test_tweet_intent.cpython-36-pytest-6.0.1.pyc ├── test_hometamon.py └── test_tweet_intent.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 # circle ci のversion 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/python:3.8 7 | environment: 8 | - DB_HOST=127.0.0.1 9 | - DB_PORT=3306 10 | - DB_DATABASE=hometask 11 | - DB_USERNAME=root 12 | - DB_PASSWORD=root 13 | 14 | working_directory: ~/hometamon 15 | 16 | steps: 17 | - checkout # code to docker image 18 | 19 | - run: 20 | name: install dependencies 21 | command: | 22 | pip install tweepy==3.8.0 23 | pip install pytest==5.4.1 24 | pip install pytest-mock==3.1.0 25 | pip install python-dotenv==0.10.5 26 | pip install mysqlclient==2.0.1 27 | 28 | - run: 29 | name: Test 30 | # cd でテストの領域を指定 31 | command: | 32 | mkdir test-reports 33 | cd main 34 | pytest 35 | 36 | # --junitxml=test-reports/junit.xml 37 | 38 | - store_test_results: 39 | path: test-reports 40 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: GitHub CI 2 | run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 3 | on: [push] 4 | jobs: 5 | Explore-GitHub-Actions: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 9 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 10 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 11 | - name: Check out repository code 12 | uses: actions/checkout@v3 13 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 14 | - run: echo "🖥️ The workflow is now ready to test your code on the runner." 15 | - name: List files in the repository 16 | run: | 17 | ls ${{ github.workspace }} 18 | 19 | - name: Set up Docker Buildx 20 | id: buildx 21 | uses: docker/setup-buildx-action@v2 22 | - name: Cache Docker Layers 23 | id: cache 24 | uses: actions/cache@v3 25 | with: 26 | path: /tmp/.build-cache 27 | key: ${{ github.ref }}-${{ github.sha }} 28 | restore-keys: 29 | ${{ runner.os }}-buildx- 30 | 31 | - name: Docker Build 32 | run: 33 | docker-compose build 34 | 35 | - name: Run Test 36 | run: | 37 | docker-compose up -d 38 | docker exec hometamon pytest tests -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | run-name: run Linter and comment to PR 3 | on: [pull_request] 4 | jobs: 5 | linter_name: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 9 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 10 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 11 | - name: Check out repository code 12 | uses: actions/checkout@v3 13 | # Install specific version black (this step is not required. default is "black[jupyter]") 14 | - run: pip install black==23.1.0 15 | - uses: reviewdog/action-black@v3 16 | with: 17 | github_token: ${{ secrets.github_token }} 18 | reporter: github-pr-check 19 | # Change reporter level if you need. 20 | # GitHub Status Check won't become failure with a warning. 21 | level: warning 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /main/src/.env 2 | .env 3 | 4 | /Django/main/hometask/local_settings.py 5 | *.pyc 6 | *.sqlite3 7 | exec 8 | 9 | .DS_Store 10 | 11 | __pycache__/ 12 | .ipynb_checkpoints/ 13 | . 14 | 15 | /main/images/stamp/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 KEN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | compose/build: 3 | docker-compose build --no-cache 4 | 5 | compose/up: 6 | docker-compose up 7 | 8 | re-run: 9 | docker-compose down 10 | docker-compose build --no-cache 11 | docker-compose up -d 12 | 13 | exec: 14 | docker exec -it hometamon /bin/sh 15 | 16 | test: 17 | docker-compose down 18 | docker-compose build 19 | docker-compose up -d 20 | docker exec hometamon pytest tests 21 | docker-compose down 22 | 23 | # run: 24 | # python3 main/src/hometamon.py --test 1 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | ## 褒めたもん 3 | hometamon 7 | 8 | [![CircleCI](https://circleci.com/gh/seven320/metamon_code.svg?style=svg)](https://circleci.com/gh/seven320/metamon_code) 9 | [![GitHub Actions CI](https://github.com/seven320/metamon_code/actions/workflows/ci.yml/badge.svg)](https://github.com/seven320/metamon_code/actions/workflows/ci.yml) 10 | [![License](https://img.shields.io/github/license/seven320/metamon_code)](https://github.com/seven320/metamon_code/stargazers) 11 | 12 | 13 | # metamon_code 14 | 褒めたもんを実行するためのコード 15 | 16 | ## Requirement 17 | ~~~ 18 | tweepy == 3.8.0 19 | python-dotenv == 0.10.5 20 | pytest == 5.4.1 21 | pytest-mock == 3.1.0 22 | Django == 3.1 23 | django-filter == 2.3.0 24 | djangorestframework == 3.11.2 25 | ~~~ 26 | 27 | ## How to USE(使い方) 28 | 29 | #### 1 clone this repository 30 | ~~~ 31 | git clone https://github.com/seven320/metamon_code.git 32 | ~~~ 33 | #### 2 get twitter api and input 34 | input your api keys into "main/src/.env" like "main/src/exam_env" 35 | ~~~ 36 | cd hometamon/main/src 37 | cp exam_env .env 38 | vim .env 39 | ~~~ 40 | 41 | #### 3 run code 42 | ~~~ 43 | python hometamon.py 44 | ~~~ 45 | #### 4deploy with docker(optional) 46 | ~~~ 47 | cd hometamon/ 48 | make re-run 49 | ~~~ 50 | 51 | #### test (optional) 52 | ~~~ 53 | cd metamon_code/main 54 | pytest 55 | ~~~ 56 | 57 | ## about hometamon (記事) 58 | [褒めたもんについて(コンセプト編)](https://denden-seven.hatenablog.com/entry/2019/01/09/131220) 59 | [褒めたもんについて(技術編)](https://denden-seven.hatenablog.com/entry/2019/01/09/130437) 60 | 61 | ## license 62 | [MIT](LICENSE) 63 | 64 | ## Author 65 | [seven320](https://github.com/seven320) 66 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | metamon: 5 | build: ./main 6 | container_name: hometamon 7 | restart: always 8 | tty: true 9 | environment: 10 | TZ: Asia/Tokyo 11 | 12 | # web: 13 | # build: ./Django/main 14 | # container_name: hometask 15 | # command: python3 manage.py runserver 0.0.0.0:8001 16 | # volumes: 17 | # - ./Django/main:/code 18 | # ports: 19 | # - "8001:8001" 20 | # environment: 21 | # TZ: Asia/Tokyo 22 | 23 | # database: 24 | # image: mysql:5.7 25 | # container_name: mysql_host 26 | # environment: 27 | # MYSQL_ROOT_PASSWORD: root 28 | # MYSQL_PASSWORD: docker 29 | # TZ: 'Asia/Tokyo' 30 | # command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 31 | # # restart: always 32 | # # 永続化 33 | # volumes: 34 | # # - ./database/mysql_data:/var/lib/mysql 35 | # - ./database/my.cnf:/etc/mysql/conf.d/my.cnf 36 | # tty: true 37 | 38 | # ports: 39 | # - 3307:3307 40 | -------------------------------------------------------------------------------- /main/.crontab_exam.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/.crontab_exam.swp -------------------------------------------------------------------------------- /main/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine 2 | 3 | RUN apk update && \ 4 | apk --no-cache add \ 5 | curl \ 6 | tmux \ 7 | git \ 8 | make \ 9 | vim \ 10 | tree \ 11 | busybox-static 12 | 13 | # library 14 | RUN pip install --upgrade pip 15 | RUN pip install \ 16 | tweepy==3.8.0 \ 17 | tqdm==4.36.1\ 18 | pykakasi==1.2\ 19 | python-dotenv==1.0.0\ 20 | pytest==5.4.1\ 21 | pytest-mock==3.1.0 22 | 23 | # RUN service cron start 24 | # RUN git clone https://github.com/seven320/metamon_code.git 25 | COPY ./src src 26 | COPY ./tests tests 27 | COPY ./images images 28 | COPY ./env env 29 | COPY ./crontab /var/spool/cron/crontabs/root 30 | CMD ["busybox", "crond", "-l", "2", "-L", "/dev/stderr", "-f"] 31 | 32 | ENV USER mother 33 | ENV HOME /home/${USER} 34 | ENV SHELL /bin/sh -------------------------------------------------------------------------------- /main/crontab: -------------------------------------------------------------------------------- 1 | */6 * * * * python3 /src/hometamon.py -------------------------------------------------------------------------------- /main/env/exam.env: -------------------------------------------------------------------------------- 1 | #twitter API 2 | CONSUMER_KEY=XXXXXXXXXXXXXXXXXXXXXXXXX 3 | CONSUMER_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 4 | ACCESS_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 5 | TOKEN_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6 | 7 | #use twitter id 8 | TWITTER_ID=XXXXXXX 9 | 10 | #admin twitter account id to get DM 11 | ADMIN_RECIPIENT_ID=XXXXXXXXX -------------------------------------------------------------------------------- /main/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/.DS_Store -------------------------------------------------------------------------------- /main/images/erai_w_newtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/erai_w_newtext.png -------------------------------------------------------------------------------- /main/images/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/icon.jpg -------------------------------------------------------------------------------- /main/images/old/halloween_mark_majo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/halloween_mark_majo.png -------------------------------------------------------------------------------- /main/images/old/halloween_pumpkin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/halloween_pumpkin1.png -------------------------------------------------------------------------------- /main/images/old/hometamon-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/hometamon-remove.png -------------------------------------------------------------------------------- /main/images/old/hometamon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/hometamon.jpg -------------------------------------------------------------------------------- /main/images/old/hometamon1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/hometamon1.jpg -------------------------------------------------------------------------------- /main/images/old/hometamon_haloween2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/hometamon_haloween2.png -------------------------------------------------------------------------------- /main/images/old/hometamon_syougatu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/hometamon_syougatu.png -------------------------------------------------------------------------------- /main/images/old/metamon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/metamon.jpeg -------------------------------------------------------------------------------- /main/images/old/metamon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/old/metamon.png -------------------------------------------------------------------------------- /main/images/otukare_w_newtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/otukare_w_newtext.png -------------------------------------------------------------------------------- /main/images/oyasumi_w_newtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/oyasumi_w_newtext.png -------------------------------------------------------------------------------- /main/images/sugoi_w_newtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/sugoi_w_newtext.png -------------------------------------------------------------------------------- /main/images/yosi_w_newtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/images/yosi_w_newtext.png -------------------------------------------------------------------------------- /main/src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /main/src/delete_friendship_byhand.py: -------------------------------------------------------------------------------- 1 | import random 2 | import hometamon 3 | 4 | def delete_by_hand(): 5 | h = hometamon.Hometamon() 6 | friends = h.api.friends_ids(h.my_twitter_user_id) 7 | random.shuffle(friends) 8 | 9 | friend_status_list = h.api.lookup_users(friends[:100]) 10 | 11 | cnt = 0 12 | for friend_status in friend_status_list: 13 | if h.exclude_user(friend_status): 14 | print(f"ユーザー名:{friend_status.name}") 15 | print(f"説明欄:{friend_status.description}") 16 | print(f"認証: {friend_status.verified}") 17 | user_input = input('上記ユーザーを忘れさせたいのであれば1入力してください。') 18 | if user_input == "1": 19 | h.api.destroy_friendship(id=friend_status.id) 20 | print("忘れました") 21 | cnt += 1 22 | else: 23 | print("覚えています") 24 | pass 25 | print(f"{cnt}人の記憶を忘れました") 26 | 27 | 28 | 29 | if __name__ == '__main__': 30 | delete_by_hand() -------------------------------------------------------------------------------- /main/src/hometamon.py: -------------------------------------------------------------------------------- 1 | # encoding utf-8 2 | import os, sys 3 | import random 4 | import datetime as dt 5 | import unicodedata 6 | 7 | from dotenv import load_dotenv 8 | 9 | import tweepy 10 | 11 | pardir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 12 | sys.path.append(pardir) 13 | 14 | from src import meta_manuscript 15 | 16 | 17 | class Hometamon: 18 | def __init__(self): 19 | if os.path.exists("/env/.env"): 20 | load_dotenv("/env/.env") 21 | elif os.path.exists("env/.env"): 22 | load_dotenv("env/.env") 23 | else: 24 | print("error doesn't exist .env path") 25 | 26 | if os.path.dirname("images"): 27 | self.image_dir = "images" 28 | elif os.path.dirname("/images"): 29 | self.image_dir = "/images" 30 | 31 | consumer_key = os.environ.get("CONSUMER_KEY") 32 | consumer_secret = os.environ.get("CONSUMER_SECRET") 33 | access_token = os.environ.get("ACCESS_TOKEN") 34 | token_secret = os.environ.get("TOKEN_SECRET") 35 | 36 | auth = tweepy.OAuthHandler( 37 | consumer_key=consumer_key, consumer_secret=consumer_secret 38 | ) 39 | auth.set_access_token(key=access_token, secret=token_secret) 40 | self.api = tweepy.API(auth, wait_on_rate_limit=True) 41 | self.my_twitter_user_id = os.environ.get("TWITTER_USER_ID") 42 | self.manuscript = meta_manuscript.Manuscript() 43 | JST = dt.timezone(dt.timedelta(hours=+9), "JST") 44 | self.JST = dt.datetime.now(JST) 45 | self.admin_twitter_id = os.environ.get("ADMIN_RECIPIENT_ID") 46 | 47 | self.exclusion_user_names = [ 48 | "bot", 49 | "ビジネス", 50 | "副業", 51 | "公式", 52 | "株", 53 | "FX", 54 | "ブランド", 55 | "無料", 56 | "キャリア", 57 | "エージェント", 58 | "LINE", 59 | "エロ", 60 | "オフパコ", 61 | "おふぱこ", 62 | "裏垢", 63 | "セフレ", 64 | "セックスレス", 65 | ] # user name 66 | self.exclusion_words = ["peing", "http"] 67 | self.exclution_descriptions = [ 68 | # 性的コンテンツ 69 | "アダルト", 70 | "エロ", 71 | "セクシャル", 72 | "18+", 73 | "グラビア", 74 | "美女", 75 | "美少女", 76 | "ヌード", 77 | "下着", 78 | "大人のおもちゃ", 79 | "風俗", 80 | "出会い系", 81 | "エッチ", 82 | "AV", 83 | "ポルノ", 84 | "裏垢", 85 | "パコ", 86 | "おふぱこ", 87 | "セフレ", 88 | "メンズエステ", 89 | "デリヘル", 90 | "スパム", 91 | "spam", 92 | "ボット", 93 | "bot", 94 | "ゲット", 95 | "無料", 96 | "キャンペーン", 97 | "アンケート", 98 | "企画", 99 | "LINE", 100 | "詐欺", 101 | "偽情報", 102 | "フェイクニュース", 103 | "fake news", 104 | "返金", 105 | "refund", 106 | "クレジットカード", 107 | "カード情報", 108 | "個人情報", 109 | "金儲け", 110 | "お金", 111 | "投資", 112 | "トレード", 113 | "FX", 114 | # 薬物や医療 115 | "医療", 116 | "薬", 117 | "ドクター", 118 | "doctor", 119 | "クリニック", 120 | "clinic", 121 | "ED", 122 | "育毛", 123 | "ダイエット", 124 | "美容", 125 | "整形", 126 | "美容外科", 127 | "健康", 128 | "ハーブ", 129 | "サプリメント", 130 | # ビジネス行為など 131 | "マルチ商法", 132 | "ネズミ講", 133 | "詐欺商法", 134 | "MLM", 135 | "ビジネスチャンス", 136 | "business opportunity", 137 | "副業", 138 | "在宅ワーク", 139 | "自由な生活", 140 | "自由な時間", 141 | "ノマドワーク", 142 | "稼ぐ", 143 | "儲ける", 144 | "年収", 145 | "月収", 146 | "資産", 147 | "利益", 148 | ] 149 | self.good_morning_words = ["おはよう", "ぽきた", "起きた", "起床", "早起き"] 150 | self.good_night_words = ["おやすみ", "寝よう", "寝る", "寝ます"] 151 | self.classify_words = [ 152 | "褒めて", 153 | "ほめて", 154 | "バオワ", 155 | "ばおわ", 156 | "バイト終", 157 | "バおわ", 158 | "実験終", 159 | "実験おわ", 160 | "らぼりだ", 161 | "ラボ離脱", 162 | "ラボりだ", 163 | "ラボリダ", 164 | "帰宅", 165 | "疲れた", 166 | "つかれた", 167 | "ちゅかれた", 168 | "仕事納め", 169 | "仕事おわり", 170 | "退勤", 171 | "仕事終わり", 172 | "掃除終", 173 | "掃除した", 174 | "がこおわ", 175 | "学校終", 176 | ] 177 | self.set_task_words = ["設定"] 178 | self.transform_words = ["変身"] 179 | self.test_words = ["__test__"] 180 | self.counts = { 181 | "ignore": 0, 182 | "praise": 0, 183 | "good_morning": 0, 184 | "good_night": 0, 185 | "pass": 0, 186 | "transform": 0, 187 | "test": 0, 188 | } 189 | 190 | def get_tweets(self): 191 | return self.api.home_timeline(count=100, since_id=None) 192 | 193 | def user_name_changer(self, user_name): 194 | # 正規化 195 | normalize_user_name = unicodedata.normalize("NFKC", user_name) 196 | if "@" in normalize_user_name: 197 | normalize_user_name = normalize_user_name.split("@")[0] 198 | return normalize_user_name 199 | 200 | def good_morning(self, tweet): 201 | # image_ratio = 0.000001 202 | reply = ( 203 | "@" 204 | + tweet.user.screen_name 205 | + "\n" 206 | + self.user_name_changer(tweet.user.name) 207 | + random.choice(self.manuscript.good_morning) 208 | ) 209 | self.counts["good_morning"] += 1 210 | # if random.random() < image_ratio: 211 | # pass 212 | # else: 213 | self.api.update_status(status=reply, in_reply_to_status_id=tweet.id) 214 | self.api.create_favorite(tweet.id) 215 | return reply 216 | 217 | def good_night(self, tweet, image_ratio=0.2): 218 | reply = ( 219 | "@" 220 | + tweet.user.screen_name 221 | + "\n" 222 | + self.user_name_changer(tweet.user.name) 223 | + random.choice(self.manuscript.good_night) 224 | ) 225 | self.counts["good_night"] += 1 226 | if random.random() < image_ratio: 227 | image_file = os.path.join(self.image_dir, "oyasumi_w_newtext.png") 228 | self.api.update_with_media( 229 | filename=image_file, status=reply, in_reply_to_status_id=tweet.id 230 | ) 231 | else: 232 | self.api.update_status(status=reply, in_reply_to_status_id=tweet.id) 233 | self.api.create_favorite(tweet.id) 234 | return reply 235 | 236 | def choose_image_by_reply(self, reply: str) -> str: 237 | # replyの言葉から画像を選ぶ 238 | image_name = "erai_w_newtext.png" 239 | for otukare in ["お疲れ", "飲む", "休"]: # 飲み物を運んでくれるようなリプライ 240 | if otukare in reply: 241 | image_name = "otukare_w_newtext.png" 242 | for yosi in ["よし", "えらい", "すごい"]: # 頭撫でるイメージのリプライ 243 | if yosi in reply: 244 | image_name = "yosi_w_newtext.png" 245 | return image_name 246 | 247 | def praise(self, tweet, image_ratio=0.2): 248 | reply = ( 249 | "@" 250 | + tweet.user.screen_name 251 | + "\n" 252 | + self.user_name_changer(tweet.user.name) 253 | + random.choice(self.manuscript.reply) 254 | ) 255 | self.counts["praise"] += 1 256 | if random.random() < image_ratio: 257 | image_name = self.choose_image_by_reply(reply) 258 | image_file = os.path.join(self.image_dir, image_name) 259 | self.api.update_with_media( 260 | filename=image_file, status=reply, in_reply_to_status_id=tweet.id 261 | ) 262 | else: 263 | self.api.update_status(status=reply, in_reply_to_status_id=tweet.id) 264 | self.api.create_favorite(tweet.id) 265 | return reply 266 | 267 | def tweet_sweet(self): 268 | status = random.choice(self.manuscript.sweet_tweet_before) 269 | status += ( 270 | "\n⊂・ー・つ" + chr(int(random.choice(self.manuscript.sweets), 16)) + "\n" 271 | ) # 16進数から変換 272 | status += random.choice(self.manuscript.sweet_tweet_after) 273 | self.api.update_status(status=status) 274 | 275 | def test_tweet_linestamp(self): 276 | reply = "ぼくのLINEスタンプがでたもん!!!ぼくのかわりにみんなをほめてほしいもん!!よろしくもん!!\nhttps://store.line.me/stickershop/product/17652748" 277 | image_file = os.path.join(self.image_dir, "stamp", "all.png") 278 | self.api.update_with_media(filename=image_file, status=reply) 279 | 280 | def test_tweet(self, image_flg=False): 281 | status = "起きてるもん!\n⊂・ー・つ" 282 | if image_flg: 283 | image_file = os.path.join(self.image_dir, "icon.jpg") 284 | self.api.update_with_media(filename=image_file, status=status) 285 | else: 286 | self.api.update_status(status=status) 287 | self.counts["test"] += 1 288 | return status 289 | 290 | def check_exclude(self, tweet): # 除外するかどうかcheck 291 | if str(tweet.user.id) == self.my_twitter_user_id: 292 | return True 293 | elif tweet.favorited: 294 | return True 295 | elif tweet.text.split(" ")[0] == "RT": 296 | return True 297 | elif tweet.text.split(" ")[0][0] == "@": 298 | if "@denden_by" in tweet.text: 299 | # hometaskの設定が入っていれば無視. 300 | if self.set_task_words[0] in tweet.text: 301 | return True 302 | else: # 自分に向けてのtweetかつ,設定が入っていないならファボ 303 | self.api.create_favorite(id=tweet.id) 304 | return True 305 | elif ( 306 | len(tweet.text) >= 80 307 | ): # if tweet is more than 80 words, it will be ignored 308 | return True 309 | for exclusion_word in self.exclusion_words: 310 | if exclusion_word in tweet.text: 311 | return True 312 | if self.exclude_user(tweet.user): # 特定のユーザー情報を含む場合除外 313 | return True 314 | return False 315 | 316 | def check_good_morning(self, tweet): # 返事するかどうかcheck 317 | if 5 <= self.JST.hour <= 9: 318 | for good_morning_word in self.good_morning_words: 319 | if good_morning_word in tweet.text: 320 | return True 321 | return False 322 | 323 | def check_good_night(self, tweet): 324 | if 22 <= self.JST.hour or self.JST.hour <= 1: 325 | for good_night_word in self.good_night_words: 326 | if good_night_word in tweet.text: 327 | return True 328 | return False 329 | 330 | # NOTE: 毎日15時にお菓子を進めるツイートをする 331 | def check_sweet(self): 332 | return self.JST.hour == 15 and 0 <= self.JST.minute <= 5 333 | 334 | # NOTE: 一ヶ月に一回宣伝ツイートを実行する 335 | def check_tweet_linestamp(self): 336 | return ( 337 | self.JST.day == 12 and self.JST.hour == 18 and 15 <= self.JST.minute <= 20 338 | ) 339 | 340 | def check_reply(self, tweet): 341 | for classify_word in self.classify_words: 342 | if classify_word in tweet.text: 343 | return True 344 | return False 345 | 346 | def check_transform(self, tweet): 347 | for transform_word in self.transform_words: 348 | if transform_word in tweet.text: 349 | return True 350 | return False 351 | 352 | def check_test(self, tweet): 353 | for test_word in self.test_words: 354 | if tweet.user.screen_name == "yosyuaomenww" and test_word in tweet.text: 355 | return True 356 | return False 357 | 358 | def check_image_flg(self, tweet): 359 | return tweet.user.screen_name == "yosyuaomenww" and "image" in tweet.text 360 | 361 | def classify(self, tweet): 362 | reply = "" 363 | if self.check_exclude(tweet): 364 | self.counts["ignore"] += 1 365 | else: 366 | if self.check_good_morning(tweet): 367 | reply = self.good_morning(tweet) 368 | elif self.check_good_night(tweet): 369 | reply = self.good_night(tweet) 370 | elif self.check_reply(tweet): 371 | reply = self.praise(tweet) 372 | elif self.check_transform(tweet): 373 | reply = self.transform() 374 | elif self.check_test(tweet): 375 | reply = self.test_tweet(image_flg=self.check_image_flg(tweet)) 376 | else: 377 | self.counts["pass"] += 1 378 | return reply 379 | 380 | def transform(self): 381 | self.counts["transform"] += 1 382 | return "" 383 | 384 | def exclude_user(self, user_status): 385 | # ユーザー名に特定の単語が入っている場合 386 | for exclusion_name in self.exclusion_user_names: 387 | if exclusion_name in user_status.name: 388 | return True 389 | if user_status.description is None: 390 | return False 391 | # ユーザーの目的欄に特定の単語が入っている場合 392 | for exclusion_description in self.exclution_descriptions: 393 | if exclusion_description in user_status.description: 394 | return True 395 | return False 396 | 397 | def follower_management(self): 398 | # フォローしているユーザーのリストを取得する処理 399 | followers = self.api.followers_ids(self.my_twitter_user_id) 400 | friends = self.api.friends_ids(self.my_twitter_user_id) 401 | follow_back = list(set(followers) - set(friends)) 402 | 403 | random.shuffle(follow_back) 404 | user_statuses = self.api.lookup_users(follow_back[:100]) 405 | cnt = 0 406 | for user_status in user_statuses: 407 | # フォローしている中で適切でないユーザー名や自己紹介欄をしているユーザーをアンフォローする処理 408 | if self.exclude_user(user_status): 409 | # self.api.destroy_friendship(id=user_status.id) 410 | # cnt += 1 411 | pass 412 | elif not user_status.follow_request_sent: 413 | try: 414 | self.api.create_friendship(id=user_status.id) 415 | cnt += 1 416 | except tweepy.error.TweepError as e: 417 | print(e) 418 | cnt += 1 419 | if cnt > 10: 420 | break 421 | 422 | def report(self): 423 | result = "time:{}\n褒めた数:{}\n除外した数:{}\n挨拶した数:{}\n反応しなかった数:{}\n変身:{}\nテスト数:{}\n合計:{}だもん!".format( 424 | self.JST.strftime("%Y/%m/%d %H:%M:%S"), 425 | self.counts["praise"], 426 | self.counts["ignore"], 427 | self.counts["good_morning"] + self.counts["good_night"], 428 | self.counts["pass"], 429 | self.counts["transform"], 430 | self.counts["test"], 431 | sum(self.counts.values()), 432 | ) 433 | self.api.send_direct_message(self.admin_twitter_id, result) 434 | print(result) 435 | return result 436 | 437 | 438 | def main(): 439 | hometamon = Hometamon() 440 | public_tweets = hometamon.get_tweets() 441 | for public_tweet in public_tweets: 442 | hometamon.classify(public_tweet) 443 | if hometamon.check_sweet(): 444 | hometamon.tweet_sweet() 445 | if hometamon.check_tweet_linestamp(): 446 | hometamon.tweet_linestamp() 447 | hometamon.follower_management() 448 | hometamon.report() 449 | 450 | 451 | if __name__ == "__main__": 452 | main() 453 | -------------------------------------------------------------------------------- /main/src/join_images.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | from matplotlib import pyplot as plt 4 | 5 | 6 | img = cv2.imread("../images/yosi_w_newtext.png") 7 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) 8 | icon = cv2.imread("../images/icon.jpg") 9 | icon = cv2.cvtColor(icon, cv2.COLOR_RGB2BGR) 10 | icon_small = cv2.resize(icon, dsize=(100, 100), interpolation=cv2.INTER_CUBIC) 11 | 12 | print(icon.shape, img.shape) 13 | 14 | circle = np.zeros((100, 100, 3)) 15 | circle = cv2.circle(circle, (50, 50), 32, (1, 1, 1), thickness=-1) 16 | 17 | circle = cv2.GaussianBlur(circle, (31, 31), 0) 18 | 19 | for i in range(3): 20 | icon_small[:, :, i] = icon_small[:, :, i] * ( 21 | np.sum(circle, axis=(2)) / 3 22 | ) + 255 * np.ones((100, 100)) * (1 - np.sum(circle, axis=(2)) / 3) 23 | 24 | 25 | print(icon_small) 26 | plt.imshow(icon_small) 27 | 28 | 29 | plt.show() 30 | -------------------------------------------------------------------------------- /main/src/make_icon.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/src/make_icon.py -------------------------------------------------------------------------------- /main/src/meta_manuscript.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | 3 | import random 4 | 5 | """ 6 | 返事ルール 7 | 8 | カタカナは使わないこと 9 | できるだけ広い意味で取られるような返事を書くこと 10 | 一人称は「僕」にすること 11 | """ 12 | 13 | 14 | class Manuscript: 15 | def __init__(self): 16 | self.tweet = [] 17 | self.reply = [ 18 | "仕事終わらせてすごいもん!!", 19 | # "つかれたもん〜", 20 | "よく頑張ったもん!!!", 21 | "満点だもん!!", 22 | # "いつも最高です。", 23 | "いつも最高だもん!!!", 24 | "とても優秀だもん!", 25 | "できる人は違うもん", 26 | "頑張り屋さんだもん", 27 | # "すごい!\nとてもすごい!!", 28 | "すごいもん!!!\nとてもすごいもん!!!!!", 29 | "すばらしいもん!", 30 | # "最高!!仕事人!!!", 31 | "最高!!仕事人だもん!!!", 32 | # "行動力の化身!!!!", 33 | "今日も頑張ってるもん", 34 | "今日の成果も素晴らしいもん!!", 35 | # "わんだふる!!!", 36 | "わんだふるだもん!!!!", 37 | "素晴らしい成果だもん!!", 38 | "素晴らしい出来栄えだもん", 39 | "仕事を頑張る姿が素敵だもん!!", 40 | "大好きだもん", 41 | "今日もお疲れ様だもん", 42 | "ゆっくり休むんだもん!!", 43 | "休憩も大事だもん", 44 | "君はひとりじゃないもん", 45 | "時には休息も必要だもん", 46 | "おつかれさまだもん", 47 | "すごいんだもん", 48 | "は偉いんだもん", 49 | "今日は大変だったもんね", 50 | "体を休めるもん\nつお布団", 51 | "今日は早く寝るもん", 52 | "よく頑張ったもん。\n僕も頑張るもん", 53 | "\nつ おふとん\nよく寝るんだもん!!", 54 | "風邪に気をつけるんだもん!!", 55 | "ゆっくり休むんだもん〜", 56 | "お疲れ様だもん", 57 | "大好きだもんよ〜", 58 | "\n今日はつかれたもんね", 59 | "ゆっくりお風呂にはいるんだもん", 60 | "えらいもん!!", 61 | # "がんばっててえらい!", 62 | "がんばっててえらいもん!!", 63 | "お疲れ様だもん", 64 | "ゆっくり休むもん", 65 | "\nいつも一緒にいるもん", 66 | "お菓子食べてゆっくり休むもん", 67 | "休憩大事もんよ", 68 | "体よく休めるもん", 69 | "は今日も頑張ったもん!!", 70 | "ご飯よく食べて休憩するもん", 71 | "甘いものでも食べてゆっくり休むもん", 72 | "よく頑張ったもんよ", 73 | "よしよしもん\nヾ⊂・ー・つなでなで", 74 | "ゆっくりするもん", 75 | "お疲れ様だもん\nヾ⊂・ー・つ応援してるもん", 76 | "応援してるもん", 77 | "いつもこっそり応援してるもん⊂・ー・)", 78 | "休憩しっかりとるもん!!", 79 | "偉いもん", 80 | "いつも頑張ってるのみてるもんよ!", 81 | "好きだもんよ", 82 | "頑張ってるもん", 83 | "はえらいんだもん!!", 84 | "ゆっくり休むもん", 85 | "ちょっと休憩するもん", 86 | "大変だけど頑張ってて偉い!!", 87 | "見守ってるもんよ,いつも頑張ってるもん", 88 | "ちょっと休むもん", 89 | "大好きだもんよ〜\nゆっくりするもん!!", 90 | "よしよしだもん!!", 91 | "今日はあったかいもの飲むもん!!", 92 | "ちょっと休むんだもん", 93 | "\n体に気をつけるもん。応援してるもん!!", 94 | "今日はご飯食べて散歩してよく寝るもん!!!", 95 | "お疲れ様だもん、ゆっくり休むもん。", 96 | "大丈夫、ぼくがついてるもん", 97 | "どんなときも、一緒にいるもん!!", 98 | "かなしいときはかなしんでもいいもん、そばにいるもん", 99 | ] 100 | 101 | self.good_morning = [ 102 | "おはようだもん!!", 103 | "起きて偉いんだもん", 104 | "おはよう。。。\nまだ眠いんだもん", 105 | "元気に頑張るもん", 106 | "今日もがんばるもん", 107 | "起きてえらいもん", 108 | # "布団から出てえらい!!", 109 | "布団から出てえらいもん!!" "おはようもん!!", 110 | "\nおはようもん\n僕ももうちょっとで起きる。もん。。。" "起きてえらいもん", 111 | "おはようもん", 112 | "おはようもん\n今日もみんなを褒めてまわるもん", 113 | "おはようだもん\n僕ももうちょっとで布団から出るもん。。。", 114 | "おはようだもん\nもう5分だけ寝たいもん。", 115 | "おはようもん!!", 116 | "おはようだもん", 117 | "ぐっどもーにんぐだもん", 118 | "今日も負けずに頑張るもん!", 119 | "朝だもんね〜", 120 | "\nまだ眠いんだもん", 121 | "頑張ろうもん!!", 122 | "おきて偉いもん", 123 | "おはようだもん", 124 | "ぐっどもーにんぐだもん", 125 | "今日もがんばるもん!!", 126 | "えらいんだもん", 127 | "\nおはようだもん⊂・ー・つ", 128 | "朝起きれて偉いんだもん!!", 129 | "起きれてえらいもん!!よしよしもん!!", 130 | "朝だよ、起きるもん!今日は一緒にでかけるもん", 131 | "", 132 | ] 133 | 134 | self.good_night = [ 135 | "お休みだもん!", 136 | "おやすみだもん!!", 137 | "おやすみだもん〜", 138 | "おやすみもん!", 139 | "よく寝るんだもん!", 140 | "いいゆめみるんだもん!", 141 | "おやすみなさいだもん", 142 | "疲れたもんね", 143 | "よしよしもん", 144 | "今日もお疲れ様だもん", 145 | "明日も頑張るもん", 146 | "おやすみだもんzzz", 147 | "ゆっくり寝るもん", 148 | "おやすみもん!!", 149 | "ゆっくり寝るもん", 150 | "おやすみなさいもん", 151 | "おやすみだもん", 152 | "ぐっどないとだもん!!", 153 | # "zzz", 154 | "よくねるもんよ", 155 | "早く寝るもん!!", 156 | "おやすみもんね\n今日は僕もすこ〜し疲れたもん", 157 | "ゆっくりねむるんだもん", 158 | "おやすみだもん\n良い夢みれるように祈ってるんだもん!!", 159 | "体冷やさないようにねるもん", 160 | "ちゃんと歯磨きしたもんか?おやすみだもん!!", 161 | "今日も頑張ったもん!!\nおやすみもん", 162 | "おやすみもん、Tweetはほどほどに寝るのも大事もん!!!", 163 | "おやすみもん、ゆっくり休むもん!!", 164 | ] 165 | 166 | self.sweet_tweet_before = [ 167 | "お菓子の時間だもんよ〜", 168 | "3時だもんね〜\nお疲れ様だもん", 169 | "頑張ってるもん", 170 | "3時だもん\n休憩するもんよ〜", 171 | "おやつの時間だもん", 172 | "3時だもんよ〜", 173 | "ちょっと一息入れるもん", 174 | "休憩するもん〜", 175 | "みんなお疲れ様だもん", 176 | "3時だもんね\nうがい,手洗いしっかりしてゆっくりたべるもん", 177 | "おやつ食べるもん", 178 | "一度休憩とるもん", 179 | "ちょっとゆっくりするもん", 180 | "こん詰めすぎもよくないもんよ。", 181 | ] 182 | 183 | self.sweet_tweet_after = [ 184 | "ゆっくりするもん", 185 | "食べてちょっとゆっくりするもん", 186 | "もう一息だもん", 187 | "頑張ってるもんよ", 188 | "休憩も大事もんよ", 189 | "もう少しだもん", 190 | "がんばるもんよ〜", 191 | ] 192 | 193 | # ref [https://lets-emoji.com/food-emoji/] 194 | self.sweets = [ 195 | "1F950", # croissant 196 | "1F95E", # pancakes 197 | "1F9C7", # waffle 198 | "1F361", # dango 199 | "1F366", # soft ice cream 200 | "1F368", # ice cream 201 | "1F369", # doughnut 202 | "1F36A", # cookie 203 | "1F370", # shortcake 204 | "1F36B", # chocolate bar 205 | "1F36E", # custard 206 | "1F36C", # candy 207 | ] 208 | 209 | self.hometask_random_reply = [ 210 | "よく頑張ったもん!!!", 211 | "とても優秀だもんよ!", 212 | "頑張り屋さんだもん", 213 | "今日も頑張ってるもんよ", 214 | "#hometask やって今日もわんだふるだもん!!!", 215 | "仕事を頑張る姿が素敵だもん!!", 216 | "今日もお疲れ様だもんよ", 217 | "おつかれさまだもんよ!", 218 | "は今日も頑張って偉いんだもん", 219 | "よしよしもん\nヾ⊂・ー・つなでなで", 220 | "大好きだもんよ〜\nゆっくりするもん!!", 221 | "よしよしだもん!!", 222 | "今日はあったかいもの飲むもん!!", 223 | "ちょっと休むんだもん", 224 | "#hometask 今日も頑張ってるもん", 225 | "#hometask できて偉いもん!!!", 226 | "お疲れ様だもん #hometask", 227 | "よく頑張ったもん #hometask", 228 | ] 229 | 230 | self.count_reply = { 231 | 1: "最初の達成だもんね.偉いもん!!この称号あげるもん\n\nつ", 232 | 5: "5回目だもん,すごいもん!!称号あげるもん!!!\n\nつ", 233 | 10: "10回目だもん!!お疲れ様だもん!!この調子で続けるもん!!!\n\nつ", 234 | 30: "30回目だもん!!この調子で頑張るもん!!!\n\nつ", 235 | } 236 | 237 | self.streak_reply = { 238 | 3: "連続3日達成だもん!!!すごいもん!!これあげるもん\n銅メダルあげるもん!\n\nつ🥉", 239 | 7: "連続7日hometask達成だもん!!!この調子で続けるもん!!\n銀メダルあげるもん\n\nつ🥈", 240 | 14: "連続14日hometask達成だもん!!偉いもん!!\nこの調子で続けるもん\n金メダルあげるもん\n\nつ🥇", 241 | } 242 | 243 | # self.rewards = [ 244 | # "U+1F435", # monkey 245 | # "U+1F98D", # gorilla 246 | # "U+1F436", # dog 247 | # "U+1F98A", # fox 248 | # "U+1F99D", # raccoon 249 | # "U+1F431", # cat 250 | # "U+1F434", # horse 251 | # "U+1F993", # zebra 252 | # "U+1F98C", # deer 253 | # "U+1F404", # cow 254 | # "U+1F417" # boar 255 | # ] 256 | 257 | self.icon = { 258 | 1: "1F95A", # egg 259 | 5: "1F423", # hatching chick 260 | 10: "1F413", # rooster 261 | 30: "1F985", # eagle 262 | } 263 | -------------------------------------------------------------------------------- /main/src/tweet_intent.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import urllib.parse 3 | 4 | 5 | def make(text, tweet_to="", url="", hashtag="", via="", related="", in_reply_to=""): 6 | base_url = "https://twitter.com/intent/tweet" 7 | 8 | if not tweet_to == "": 9 | text = "@{} ".format(tweet_to) + text 10 | 11 | query_dic = { 12 | "text": text, 13 | "url": url, 14 | "hashtags": hashtag, 15 | "via": via, 16 | "in_reply_to": in_reply_to, 17 | } 18 | 19 | parameters = urllib.parse.urlencode(query_dic) 20 | 21 | if len(parameters) > 0: 22 | web_intent_url = base_url + "?" + parameters 23 | else: 24 | web_intent_url = base_url 25 | 26 | return web_intent_url 27 | 28 | 29 | if __name__ == "__main__": 30 | intent_url = make() 31 | print(intent_url) 32 | -------------------------------------------------------------------------------- /main/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__init__.py -------------------------------------------------------------------------------- /main/tests/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_access_django.cpython-36-pytest-6.0.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_access_django.cpython-36-pytest-6.0.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_hometamon.cpython-36-pytest-5.4.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_hometamon.cpython-36-pytest-5.4.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_hometamon.cpython-36-pytest-6.0.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_hometamon.cpython-36-pytest-6.0.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_hometamon.cpython-38-pytest-5.4.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_hometamon.cpython-38-pytest-5.4.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_hometask.cpython-36-pytest-6.0.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_hometask.cpython-36-pytest-6.0.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_maketweet_intent.cpython-36-pytest-6.0.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_maketweet_intent.cpython-36-pytest-6.0.1.pyc -------------------------------------------------------------------------------- /main/tests/__pycache__/test_tweet_intent.cpython-36-pytest-6.0.1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven320/metamon_code/d8469f3207d20cb415f7478499430cb119dd23ae/main/tests/__pycache__/test_tweet_intent.cpython-36-pytest-6.0.1.pyc -------------------------------------------------------------------------------- /main/tests/test_hometamon.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import random 4 | 5 | import pytest 6 | from unittest.mock import patch 7 | import datetime as dt 8 | 9 | from src import hometamon 10 | 11 | 12 | class Test_Hometamon: 13 | @pytest.fixture() 14 | def app(self, mocker): 15 | app = hometamon.Hometamon() 16 | app.manuscript = mocker.Mock() 17 | app.manuscript.reply = ["お疲れ様だもん"] 18 | app.manuscript.good_morning = ["おはようだもん"] 19 | app.manuscript.good_night = ["おやすみだもん"] 20 | app.manuscript.sweet_tweet_before = ["3時"] 21 | app.manuscript.sweet_tweet_after = ["休憩するもん"] 22 | app.manuscript.sweets = ["1F950"] # croissant 23 | app.my_twitter_user_id = "966247026416472064" 24 | app.api = mocker.MagicMock() 25 | return app 26 | 27 | @pytest.fixture() 28 | def tweet(self, mocker): 29 | tweet = mocker.MagicMock() 30 | tweet.text = "おはよう" 31 | tweet.user.name = "電電" 32 | tweet.user.screen_name = "yosyuaomenww" 33 | tweet.user.id = 555555 34 | tweet.favorited = False 35 | tweet.id = 123 36 | return tweet 37 | 38 | @pytest.fixture() 39 | def task_tweet(self, mocker): 40 | task_tweet = mocker.MagicMock() 41 | task_tweet.text = "@denden_by\nsettask\n本を読む" 42 | task_tweet.user.name = "ココア" 43 | task_tweet.user.screen_name = "cocoa" 44 | task_tweet.favorited = False 45 | task_tweet.id = 987654 46 | return task_tweet 47 | 48 | @pytest.fixture() 49 | def user_status(self, mocker): 50 | user_status = mocker.MagicMock() 51 | user_status.name = "電電" 52 | user_status.screen_name = "yosyuaomenww" 53 | user_status.id = 5555555555 54 | user_status.url = "https://developer.twitter.com" 55 | user_status.description = "I love cats!!!!" 56 | user_status.statuses_count = 1000 # num of tweets 57 | user_status.followers_count = 100 58 | user_status.friends_count = 100 # following count 59 | return user_status 60 | 61 | class Test_初期値がうまく読み込めているか調べる: 62 | def test_image_dir(self, app): 63 | assert app.image_dir in ["images", "/images"] 64 | 65 | class Test_スクリーンネームからat以降の文字を削除する: 66 | def test_user_screen_name_changer_small(self, app): 67 | assert app.user_name_changer("電電@テスト頑張る") == "電電" 68 | 69 | def test_user_screen_name_changer_big(self, app): 70 | assert app.user_name_changer("電電@テスト頑張る") == "電電" 71 | 72 | def test_user_screen_name_changer(self, app): 73 | assert app.user_name_changer("電電") == "電電" 74 | 75 | class Test_朝の挨拶を行う: 76 | def test_good_morning(self, app, tweet): 77 | expected = "@yosyuaomenww\n電電おはようだもん" 78 | assert app.good_morning(tweet) == expected 79 | app.api.update_status.assert_called_once_with( 80 | status=expected, in_reply_to_status_id=123 81 | ) 82 | app.api.create_favorite.assert_called_once_with(tweet.id) 83 | 84 | def test_good_morning_with_image(self): 85 | pass 86 | 87 | class Test_夜の挨拶を行う: 88 | def test_good_night(self, app, tweet, mocker): 89 | expected = "@yosyuaomenww\n電電おやすみだもん" 90 | random.random = mocker.Mock(return_value=1) 91 | assert app.good_night(tweet) == expected 92 | app.api.update_status.assert_called_once_with( 93 | status=expected, in_reply_to_status_id=123 94 | ) 95 | app.api.create_favorite.assert_called_once_with(tweet.id) 96 | 97 | def test_good_night_with_image(self, app, tweet, mocker): 98 | expected = "@yosyuaomenww\n電電おやすみだもん" 99 | random.random = mocker.Mock(return_value=0) 100 | assert app.good_night(tweet) == expected 101 | app.api.update_with_media.assert_called_once_with( 102 | filename=os.path.join(app.image_dir, "oyasumi_w_newtext.png"), 103 | status=expected, 104 | in_reply_to_status_id=tweet.id, 105 | ) 106 | app.api.create_favorite.assert_called_once_with(tweet.id) 107 | 108 | class Test_褒める: 109 | def test_praise(self, app, tweet, mocker): 110 | random.random = mocker.Mock(return_value=1) 111 | expected = "@yosyuaomenww\n電電お疲れ様だもん" 112 | assert app.praise(tweet) == expected 113 | app.api.update_status.assert_called_once_with( 114 | status=expected, in_reply_to_status_id=tweet.id 115 | ) 116 | app.api.create_favorite.assert_called_once_with(tweet.id) 117 | 118 | # def test_praise_with_image(self, app, tweet, mocker): 119 | # random.random = mocker.Mock(return_value = 0) 120 | # expected = "@yosyuaomenww\n電電お疲れ様だもん" 121 | # assert app.praise(tweet) == expected 122 | # app.api.update_with_media.assert_called_once_with( 123 | # filename = "images/icon.jpg", 124 | # status = expected, 125 | # in_reply_to_status_id = tweet.id 126 | # ) 127 | # app.api.create_favorite.assert_called_once_with( 128 | # tweet.id 129 | # ) 130 | 131 | class Test_休憩を促すツイートを行う: 132 | def test_tweet_sweet(self, app): 133 | expected = "3時\n⊂・ー・つ🥐\n休憩するもん" 134 | app.tweet_sweet() 135 | app.api.update_status.assert_called_once_with(status=expected) 136 | 137 | class Test_LINEスタンプの宣伝ツイートを行う: 138 | def test_tweet_linestamp(self, app): 139 | expected = "ぼくのLINEスタンプがでたもん!!!ぼくのかわりにみんなをほめてほしいもん!!よろしくもん!!\nhttps://store.line.me/stickershop/product/17652748" 140 | app.test_tweet_linestamp() 141 | app.api.update_with_media.assert_called_once_with( 142 | filename=os.path.join(app.image_dir, "stamp", "all.png"), 143 | status=expected, 144 | ) 145 | 146 | class Test_電電のテストツイートに対して反応する: 147 | def test_test_tweet(self, app): 148 | expected = "起きてるもん!\n⊂・ー・つ" 149 | assert app.test_tweet() == expected 150 | app.api.update_status.assert_called_once_with(status=expected) 151 | 152 | def test_test_tweet_with_image(self, app): 153 | expected = "起きてるもん!\n⊂・ー・つ" 154 | assert app.test_tweet(image_flg=True) == expected 155 | app.api.update_with_media.assert_called_once_with( 156 | filename=os.path.join(app.image_dir, "icon.jpg"), status=expected 157 | ) 158 | 159 | class Test_定められたツイートに対しては反応しない: 160 | def test_check_exclude_text(self, app, tweet): 161 | assert app.check_exclude(tweet) == False 162 | 163 | def test_check_exclude_text_with_favorited(self, app, tweet, mocker): 164 | assert ( 165 | app.check_exclude(mocker.patch.object(tweet, "method", favorited=True)) 166 | == True 167 | ) 168 | 169 | def test_check_exclude_text_with_RT(self, app, tweet, mocker): 170 | assert ( 171 | app.check_exclude( 172 | mocker.patch.object( 173 | tweet, "method", text="RT おはよう", favorited=False 174 | ) 175 | ) 176 | == True 177 | ) 178 | 179 | def test_check_exclude_text_with_at(self, app, tweet, mocker): 180 | assert ( 181 | app.check_exclude( 182 | mocker.patch.object( 183 | tweet, "method", text="@yosyuaomew おはよう", favorited=False 184 | ) 185 | ) 186 | == True 187 | ) 188 | 189 | def test_check_exclude_text_with_long_tweet(self, app, tweet, mocker): 190 | assert ( 191 | app.check_exclude( 192 | mocker.patch.object(tweet, "method", text="*" * 80, favorited=False) 193 | ) 194 | == True 195 | ) 196 | 197 | def test_check_exclude_text_with_url(self, app, tweet, mocker): 198 | assert ( 199 | app.check_exclude( 200 | mocker.patch.object( 201 | tweet, "method", text="https://www.google.com/", favorited=False 202 | ) 203 | ) 204 | == True 205 | ) 206 | 207 | def test_check_exclude_text_with_at_me(self, app, tweet, mocker): 208 | assert ( 209 | app.check_exclude( 210 | mocker.patch.object( 211 | tweet, 212 | "method", 213 | text="@denden_by ありがとう", 214 | favorited=False, 215 | id=123, 216 | ) 217 | ) 218 | == True 219 | ) 220 | 221 | def test_check_exclude_text_with_mytweet(self, app, tweet, mocker): 222 | mocker.patch.object(tweet.user, "id", 966247026416472064) 223 | assert app.check_exclude(tweet) == True 224 | 225 | def test_check_exclude_with_exclude_user(self, app, tweet): 226 | tweet.user.name = "botほげ" 227 | assert app.check_exclude(tweet) == True 228 | 229 | def test_check_exclude_with_exclude_description(self, app, tweet): 230 | tweet.user.description = "セフレになりたいな(///∇︎///) " 231 | assert app.check_exclude(tweet) == True 232 | 233 | class Test_日本時間の5時00分_9時59分の時Trueを返す: 234 | def test_check_good_morning_with_night(self, app, tweet): 235 | app.JST = dt.datetime(2020, 11, 11, 4, 59) 236 | assert app.check_good_morning(tweet) == False 237 | 238 | def test_check_good_morning_with_early_morning(self, app, tweet): 239 | app.JST = dt.datetime(2020, 11, 11, 8, 0) 240 | assert app.check_good_morning(tweet) == True 241 | 242 | def test_check_good_morning_with_noon(self, app, tweet): 243 | app.JST = dt.datetime(2020, 11, 11, 10, 0) 244 | assert app.check_good_morning(tweet) == False 245 | 246 | class Test_日本時間の22時00分_1時59分の時Trueを返す: 247 | def test_check_good_night(self, app, tweet): 248 | tweet.text = "おやすみ" 249 | app.JST = dt.datetime(2020, 11, 11, 21, 59) 250 | assert app.check_good_night(tweet) == False 251 | 252 | def test_check_good_night_with_last(self, app, tweet): 253 | tweet.text = "おやすみ" 254 | app.JST = dt.datetime(2020, 11, 11, 0, 1) 255 | assert app.check_good_night(tweet) == True 256 | 257 | def test_check_good_night_with_morning(self, app, tweet): 258 | tweet.text = "おやすみ" 259 | app.JST = dt.datetime(2020, 11, 11, 2, 00) 260 | assert app.check_good_night(tweet) == False 261 | 262 | class Test_日本時間の15時にTrueを返す: 263 | def test_check_sweet_before(self, app): 264 | app.JST = dt.datetime(2020, 11, 11, 14, 59) 265 | assert app.check_sweet() == False 266 | 267 | def test_check_sweet_with_15(self, app): 268 | app.JST = dt.datetime(2020, 11, 11, 15, 0) 269 | assert app.check_sweet() == True 270 | 271 | def test_check_sweet_with_after(self, app): 272 | app.JST = dt.datetime(2020, 12, 25, 20, 8) 273 | assert app.check_sweet() == False 274 | 275 | class Test_日本時間の12日の18時15から18時20にTrueを返す: 276 | def test_check_tweet_linestamp_with_False(self, app, tweet): 277 | app.JST = dt.datetime(2022, 12, 12, 18, 21) 278 | assert app.check_tweet_linestamp() == False 279 | 280 | def test_check_tweet_linestamp_with_True(self, app, tweet): 281 | app.JST = dt.datetime(2022, 12, 12, 18, 18) 282 | assert app.check_tweet_linestamp() == True 283 | 284 | class Test_返事をするかどうか: 285 | def test_check_reply(self, app, tweet, mocker): 286 | assert ( 287 | app.check_reply(mocker.patch.object(tweet, "method", text="疲れた")) 288 | == True 289 | ) 290 | 291 | def test_check_reply_with_false(self, app, tweet, mocker): 292 | assert ( 293 | app.check_reply(mocker.patch.object(tweet, "method", text="元気いっぱい")) 294 | == False 295 | ) 296 | 297 | class Test_予備機能_変身: 298 | def test_check_transform(self, app, tweet, mocker): 299 | assert ( 300 | app.check_transform(mocker.patch.object(tweet, "method", text="変身")) 301 | == True 302 | ) 303 | 304 | def test_check_transform_with_false(self, app, tweet, mocker): 305 | assert ( 306 | app.check_transform(mocker.patch.object(tweet, "method", text="返信")) 307 | == False 308 | ) 309 | 310 | class Test_test_tweetに対してツイートするかどうか: 311 | class Test_起動ツイート: 312 | def test_check_text(self, app, tweet): 313 | tweet.text = "__test__" 314 | tweet.user.screen_name = "yosyuaomenww" 315 | assert app.check_test(tweet) == True 316 | 317 | def test_check_text_with_false(self, app, tweet): 318 | tweet.text = "__test__" 319 | tweet.user.screen_name = "hogehoge" 320 | assert app.check_test(tweet) == False 321 | 322 | class Test_imageを含んだ起動ツイート: 323 | def test_check_image_flg(self, app, tweet): 324 | tweet.text = "__test__ image" 325 | assert app.check_image_flg(tweet) == True 326 | 327 | class Test_フォローしてきたユーザーのうちランダムに10人フォローバックする: 328 | def test_exclude_user(self, app, user_status): 329 | assert app.exclude_user(user_status) == False 330 | 331 | def test_exclude_user_with_exclude_description(self, app, user_status): 332 | user_status.description = "裏垢はじめました音符リアルな出会いが欲しいです" 333 | assert app.exclude_user(user_status) == True 334 | 335 | def test_follower_management(self, app, mocker): 336 | app.api.followers_ids.return_value = [ 337 | 1220747547607650304, 338 | 1125305225198297089, 339 | ] 340 | app.api.friends_ids.return_value = [1220747547607650304] 341 | user_status = mocker.Mock() 342 | user_status.name = "abap" 343 | user_status.follow_request_sent = False 344 | user_status.id = 1125305225198297089 345 | user_status.description = None 346 | app.api.lookup_users.return_value = [user_status] 347 | app.follower_management() 348 | app.api.create_friendship.assert_called_once_with(id=1125305225198297089) 349 | 350 | app.api.reset_mock() # 呼び出し回数をリセット 351 | user_status.follow_request_sent = True 352 | app.api.lookup_users.return_value = [user_status] 353 | app.follower_management() 354 | app.api.create_friendship.assert_not_called() 355 | 356 | def test_follower_management_with_exclution_user(self, app, mocker): 357 | app.api.followers_ids.return_value = [ 358 | 1220747547607650304, 359 | 1125305225198297089, 360 | ] 361 | app.api.friends_ids.return_value = [1220747547607650304] 362 | user_status = mocker.Mock() 363 | user_status.name = "abap" 364 | user_status.follow_request_sent = False 365 | user_status.id = 1125305225198297089 366 | user_status.description = "セフレ募集中" 367 | app.api.lookup_users.return_value = [user_status] 368 | app.follower_management() 369 | app.api.create_friendship.assert_not_called() 370 | 371 | class Test_実行した行動のログをyosyuaomenwwに送信する: 372 | def test_report(self, app): 373 | app.JST = dt.datetime(2020, 4, 27, 17, 40, 30) 374 | app.admin_twitter_id = 999 375 | app.report() 376 | app.api.send_direct_message.assert_called_once_with( 377 | 999, 378 | "time:2020/04/27 17:40:30\n褒めた数:0\n除外した数:0\n挨拶した数:0\n反応しなかった数:0\n変身:0\nテスト数:0\n合計:0だもん!", 379 | ) 380 | 381 | ################# 382 | ### Join test ### 383 | ################# 384 | class Test_ツイート内容に基づいた分類とその反応ができている: 385 | def test_classify_with_false(self, app, tweet): 386 | tweet.text = "http" 387 | expected = "" 388 | assert app.classify(tweet) == expected 389 | app.api.update_statussert_called_once_with( 390 | status=expected, in_reply_to_status_id=tweet.id 391 | ) 392 | app.api.create_favorite.assert_not_called() 393 | 394 | class Test_おはよう: 395 | def test_classify_good_morning(self, app, tweet): 396 | tweet.text = "おはよう" 397 | tweet.user.name = "青い鳥" 398 | expected = "@yosyuaomenww\n青い鳥おはようだもん" 399 | app.JST = dt.datetime(2020, 2, 21, 8, 0) 400 | assert app.classify(tweet) == expected 401 | app.api.update_status.assert_called_once_with( 402 | status=expected, in_reply_to_status_id=tweet.id 403 | ) 404 | app.api.create_favorite.assert_called_once_with(tweet.id) 405 | 406 | class Test_おやすみ: 407 | def test_classify_goodnight(self, app, tweet, mocker): 408 | random.random = mocker.Mock(return_value=1) 409 | tweet.text = "寝る" 410 | expected = "@yosyuaomenww\n電電おやすみだもん" 411 | app.JST = dt.datetime(2020, 2, 21, 22, 0) 412 | 413 | assert app.classify(tweet) == expected 414 | app.api.update_status.assert_called_once_with( 415 | status=expected, in_reply_to_status_id=tweet.id 416 | ) 417 | app.api.create_favorite.assert_called_once_with(tweet.id) 418 | 419 | def test_classify_goodnight_with_image(self, app, tweet, mocker): 420 | random.random = mocker.Mock(return_value=0) 421 | tweet.text = "寝る" 422 | expected = "@yosyuaomenww\n電電おやすみだもん" 423 | app.JST = dt.datetime(2020, 2, 21, 22, 0) 424 | assert app.classify(tweet) == expected 425 | app.api.update_with_media.assert_called_once_with( 426 | filename=os.path.join(app.image_dir, "oyasumi_w_newtext.png"), 427 | status=expected, 428 | in_reply_to_status_id=tweet.id, 429 | ) 430 | app.api.create_favorite.assert_called_once_with(tweet.id) 431 | 432 | class Test_褒める: 433 | def test_classify(self, app, tweet, mocker): 434 | tweet.text = "疲れた" 435 | expected = "@yosyuaomenww\n電電お疲れ様だもん" 436 | random.random = mocker.Mock(return_value=1) 437 | assert app.classify(tweet) == expected 438 | app.api.update_status.assert_called_once_with( 439 | status=expected, in_reply_to_status_id=tweet.id 440 | ) 441 | app.api.create_favorite.assert_called_once_with(tweet.id) 442 | 443 | def test_classify_with_image(self, app, tweet, mocker): 444 | tweet.text = "疲れた" 445 | expected = "@yosyuaomenww\n電電お疲れ様だもん" 446 | random.random = mocker.Mock(return_value=0) 447 | assert app.classify(tweet) == expected 448 | app.api.update_with_media.assert_called_once_with( 449 | filename=os.path.join(app.image_dir, "otukare_w_newtext.png"), 450 | status=expected, 451 | in_reply_to_status_id=tweet.id, 452 | ) 453 | app.api.create_favorite.assert_called_once_with(tweet.id) 454 | 455 | def test_classify_with_false(self, app, tweet): 456 | tweet.text = "今日のメニューはカレーだ" 457 | expected = "" 458 | assert app.classify(tweet) == expected 459 | app.api.update_status.assert_not_called() 460 | app.api.create_favorite.assert_not_called() 461 | 462 | def test_choose_image_by_reply(self, app): 463 | reply = "好きだもんよ" 464 | assert app.choose_image_by_reply(reply) == "erai_w_newtext.png" 465 | 466 | def test_choose_image_by_reply_yosi(self, app): 467 | reply = "えらいもん" 468 | assert app.choose_image_by_reply(reply) == "yosi_w_newtext.png" 469 | 470 | def test_choose_image_by_reply_otu(self, app): 471 | reply = "お疲れ様だもん" 472 | assert app.choose_image_by_reply(reply) == "otukare_w_newtext.png" 473 | 474 | class Test_テストツイート: 475 | def test_classify(self, app, tweet): 476 | tweet.text = "__test__" 477 | expected = "起きてるもん!\n⊂・ー・つ" 478 | assert app.classify(tweet) == expected 479 | app.api.update_status.assert_called_once_with(status=expected) 480 | expected = "" 481 | tweet.user.screen_name = "twitter" 482 | assert app.classify(tweet) == expected 483 | 484 | def test_classify_with_image(self, app, tweet): 485 | tweet.text = "__test__ image" 486 | expected = "起きてるもん!\n⊂・ー・つ" 487 | assert app.classify(tweet) == expected 488 | app.api.update_with_media.assert_called_once_with( 489 | status=expected, filename=os.path.join(app.image_dir, "icon.jpg") 490 | ) 491 | 492 | class Test_変身: 493 | def test_transform(self, app): 494 | expected = "" 495 | assert app.transform() == expected 496 | assert app.counts["transform"] == 1 497 | -------------------------------------------------------------------------------- /main/tests/test_tweet_intent.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | 3 | from src import tweet_intent 4 | 5 | 6 | def test_make_intent_url(): 7 | text = "早く寝る" 8 | intent_url = tweet_intent.make(text) 9 | parameters = intent_url.split("?")[-1] 10 | dic = urllib.parse.parse_qs(parameters) 11 | assert dic["text"][0] == text 12 | 13 | 14 | def test_make_intent_url_with_hashtag(): 15 | text = "task を設定" 16 | hashtag = "hometask" 17 | intent_url = tweet_intent.make(text, hashtag=hashtag) 18 | parameters = intent_url.split("?")[-1] 19 | dic = urllib.parse.parse_qs(parameters) 20 | assert dic["text"][0] == text 21 | assert dic["hashtags"][0] == hashtag 22 | 23 | 24 | def test_make_intent_url_with_tweet_to(): 25 | text = "task を設定" 26 | tweet_to = "denden_by" 27 | intent_url = tweet_intent.make(text, tweet_to=tweet_to) 28 | parameters = intent_url.split("?")[-1] 29 | dic = urllib.parse.parse_qs(parameters) 30 | assert dic["text"][0] == "@{} ".format(tweet_to) + text 31 | --------------------------------------------------------------------------------