💕

TweetinviでTwitterブロック機能

2021/11/27に公開

作成経緯

Twitterでエロ垢から「リストに入れられる・フォローされる」頻度が多く、手動でのブロックが面倒なので定期実行の自動ブロック機能を作りました

要件

  • 動作頻度は日毎
  • 自分が入っているリストを確認し、エロ垢がオーナーであればブロックする
  • 自分のフォロワーを確認し、エロ垢がいたらブロックする
  • 使用者は自分のみ

エロ垢判定方法

エロ垢出現場所 ブロック条件
リスト ・自分がリストオーナーを非フォロー
・リスト名・リストのオーナーの自己紹介文文字列に自分が用意した「ngワード」が入っている
フォロー ・自分が非フォロー
・フォロワーの自己紹介文文字列に自分が用意した「ngワード」が入っている

使用するもの

コード

流れ

  1. 自分がフォローしているユーザーのIDを取得
  2. 自分が入っているリストを取得
  3. 自分がリストオーナーを非フォロー、かつリスト名かリストオーナーの自己紹介文に「ngワード」が入っていたらブロック
  4. フォロワーを取得
  5. 自分がフォロワーを非フォロー、かつフォロワーの自己紹介文に「ngワード」が入っていたらブロック
  6. ブロックしたユーザーを自分のDMに通知
using Tweetinvi;
using System.Text;
using System.Linq;
using Tweetinvi.Parameters;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace BlockTwitterAccouts
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var userClient = new TwitterClient(args[0], args[1], args[2], args[3]);
            //自分のユーザー情報を取得
            var user = await userClient.Users.GetAuthenticatedUserAsync();
            //自分がフォローしているユーザーのIDを取得(全て)
            var followUserIds = await userClient.Users.GetFriendIdsAsync(user.Id);
            var followUserIdsSet = new HashSet<long>(followUserIds);
            
            var blockingUserList = new List<string>();

            var blockingUserFromList = await blockUserFromList(userClient, followUserIdsSet);
            blockingUserList.AddRange(blockingUserFromList);

            var blockingUserFromFollow = await blockUserFromFollowers(userClient, user.Id, followUserIdsSet);
            blockingUserList.AddRange(blockingUserFromFollow);

            //エロ垢があったらDMを送る
            if(blockingUserList.Any()){
                var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
                var dmMessage = $"【自動ブロック機能】\n{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}\n以下のアカウントをブロックしました\n\n@{string.Join( "\n@", blockingUserList)}";
                //自分にDMを送信
                await userClient.Messages.PublishMessageAsync(dmMessage, user.Id);
            }
        }

        /// <summary>
        /// フォロワーにエロ垢がいたらブロックする
	/// <summary>
        private static async Task<List<string>> blockUserFromFollowers(TwitterClient userClient, long userId, HashSet<long> followUserIdsSet){
            var blockUserList = new List<string>();
            //フォロワーを100件取得
            var followers = await userClient.Users.GetFollowersAsync(userId);
            foreach(var follower in followers){
                //自分がフォローしていたら判定しない
                if(followUserIdsSet.Contains(follower.Id)){
                    continue;
                }
                //自己紹介文のngワード判定
                if(isNgByUserDescription(follower.Description)){
                    //エロ垢をブロックする
                    await userClient.Users.BlockUserAsync(follower.Id);
                    blockUserList.Add(follower.ScreenName);
                }
            }
            return blockUserList;
        }

        /// <summary>
        /// 自分が入っているリストのオーナーにエロ垢がいたらブロックする
	/// <summary>
        private static async Task<List<string>> blockUserFromList(TwitterClient userClient, HashSet<long> followUserIds){
            //自分が入っているリストの取得
            var lists = await userClient.Lists.GetAccountListMembershipsAsync();
            var blockUserList = new List<string>();
            foreach(var list in lists){
                //自分がフォローしていたら判定しない
                if(followUserIds.Contains(list.Owner.Id)){
                    continue;
                }
                //リスト名・自己紹介文のngワード判定
                if(isNgByName(list.Name) || isNgByUserDescription(list.Owner.Description)){
                    //エロ垢をブロックする
                    await userClient.Users.BlockUserAsync(list.Owner.Id);
                    blockUserList.Add(list.Owner.ScreenName);
                }
            }
            return blockUserList;
        }

        /// <summary>
        /// リスト名にngワードが入っていればtrueを返す
	/// <summary> 
        private static bool isNgByName(string listName){
            //「list_name.csv」はリスト名のngワードが入ったcsvファイル
            var ngWordList = getCsvData("./ngWordText/list_name.csv");
            foreach(var ngWord in ngWordList){
                if(listName.Contains(ngWord)){
                    return true;
                }
            }
            return false;
        } 

        /// <summary>      
        /// ユーザーの自己紹介文にngワードが入っていればtrueを返す
        /// <summary>  
        private static bool isNgByUserDescription(string userDescription){
            //「user_description.csv」は自己紹介文のngワードが入ったcsvファイル
            var ngWordList = getCsvData("./ngWordText/user_description.csv");
            foreach(var ngWord in ngWordList){
                if(userDescription.Contains(ngWord)){
                    return true;
                }
            }
            return false;
        }

        /// <summary>  
        /// csvファイルから文字列を読み取って配列にして返す
	/// <summary> 
        private static string[] getCsvData(string filePath){
            var file = File.ReadAllText(filePath, Encoding.UTF8);
            return file.Split(',');
        } 
    }
}

GitHubActions

流れ

  1. UTC時間で毎日9時(多少ずれる)にビルドして実行する
name: BuildAndRun

on:
  # 毎日UTC時間の9時(多少ずれる)にビルドして実行
  # 日本の時間で18時過ぎくらいに実行されます
  schedule: 
    - cron:  '0 9 * * *'
  # GitHubActionsのページから手動で実行
  workflow_dispatch:

jobs:
  BuildAndRunJob:
    runs-on: ubuntu-latest
    container: docker://mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim

    steps:
    # Checkout V2
    # https://github.com/actions/checkout
    - uses: actions/checkout@v2
    - run: dotnet --version 
    # ビルド+コマンドライン引数を渡して実行
    - run: cd blockTwitterAccounts2 && dotnet run -- ${API_KEY} ${API_KEY_SECRET} ${ACCESS_TOKEN} ${ACCESS_TOKEN_SECRET}
      env:
        # 暗号化されたシークレット
        # https://docs.github.com/ja/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository
        API_KEY: ${{ secrets.API_KEY }}        
        API_KEY_SECRET: ${{ secrets.API_KEY_SECRET }}
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}        
        ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }}

リポジトリ

https://github.com/aoaoaoaoaoaoaoi/block_twitter_accounts_2_for_public

Discussion