diff --git a/README.md b/README.md index 7408c20..0daff98 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Botometer Python API +[![PyPI version](https://badge.fury.io/py/botometer.svg)](https://badge.fury.io/py/botometer) -A Python API for [Botometer by OSoMe](https://osome.iu.edu). +# Botometer X Python API + +A Python API for [Botometer X by OSoMe](https://osome.iu.edu). Previously known as `botornot-python`. Behind the scenes, this uses the Botometer's HTTP endpoint, available via @@ -8,289 +10,108 @@ Behind the scenes, this uses the Botometer's HTTP endpoint, available via RapidAPI usage/account related questions should be posted on RapidAPI discussion. -## [Change Note] - -### September, 2020 +## [Change Note/Announcement] -We have a major update for Botometer: +### June, 2024 -1. Botometer has been upgraded to V4, and you can use the `/4/check_account` endpiont to access it. -2. The response of `/4/check_account` is reorganized. -3. A new endpoint for BotometerLite is added. It allows checking accounts in bulk. +We are releasing a new API endpoint for Botometer X. -You can see the full [announcement](https://cnets.indiana.edu/blog/2020/09/01/botometer-v4/) for details. +Unlike the original Botometer that fetched data from Twitter and calculated bot scores on the fly, Botometer X is in archival mode and relies on pre-calculated scores based on historical data collected before June 2023. +The API endpoint allows users to fetch scores in bulk using a list of user ids or screen names, without the need of a Twitter/X's developer account. -Due to the update, please upgrade `botometer-python` in your local environment to the newest version. -You may also need to modify your code to adapt to the new response from the API. -For more information, check out the documentation below. -If you want to try the new BotometerLite API, checkout the documentation below. +For details of Botometer X, please refer to the [FQA](https://botometer.osome.iu.edu/faq). -### May, 2020 - -We have made some changes to our API, please read the [announcement](https://twitter.com/Botometer/status/1250557098708144131) for details. Due to the API change, the old `botometer-python` package might stop to work. Please upgrade it in your local environment to the newest version. ## Help -> You probably want to have a look at [Troubleshooting & FAQ](https://github.com/IUNetSci/botometer-python/wiki/Troubleshooting-&-FAQ) in the wiki. Please feel free to suggest and/or contribute improvements to that page. +> You probably want to have a look at [Troubleshooting & FAQ](https://github.com/osome-iu/botometer-python/wiki/Troubleshooting-&-FAQ) in the wiki. Please feel free to suggest and/or contribute improvements to that page. ## Prior to Utilizing Botometer -To begin using Botometer, you must follow the steps below before running any code: +To begin using Botometer X, you must follow the steps below before running any code: 1. Create a free [RapidAPI](https://rapidapi.com/) account. 2. Subscribe to [Botometer Pro](https://rapidapi.com/OSoMe/api/botometer-pro) on RapidApi by selecting a plan. > There is a completely free version (which does not require any credit card information) for testing purposes. -3. Create a Twitter application via https://developer.twitter.com/ - > Botometer utilizes the access credentials provided by Twitter for the application. -4. Ensure Botometer Pro's dependencies are already installed. +3. Ensure Botometer Pro's dependencies are already installed. > See the [Dependencies](#dependencies) section for details. -**Note:** These steps are necessary to access credentials and download other packages which are needed for Botometer to work properly. Please see [RapidAPI and Twitter Access Details](#access) below for more details on this topic. - ## Quickstart -From your command shell, run +From your command shell, run ``` pip install botometer ``` -### Botometer-V4 -To access the Botometer-V4 API, enter something like this in a Python shell or script: +### Botometer X + +To access the Botometer X endpoint, enter something like this in a Python shell or script: ```python import botometer rapidapi_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -twitter_app_auth = { - 'consumer_key': 'xxxxxxxx', - 'consumer_secret': 'xxxxxxxxxx', - 'access_token': 'xxxxxxxxx', - 'access_token_secret': 'xxxxxxxxxxx', - } -bom = botometer.Botometer(wait_on_ratelimit=True, - rapidapi_key=rapidapi_key, - **twitter_app_auth) - -# Check a single account by screen name -result = bom.check_account('@clayadavis') - -# Check a single account by id -result = bom.check_account(1548959833) - -# Check a sequence of accounts -accounts = ['@clayadavis', '@onurvarol', '@jabawack'] -for screen_name, result in bom.check_accounts_in(accounts): - # Do stuff with `screen_name` and `result` -``` -Result: -```json -{ - "cap": { - "english": 0.8018818614025648, - "universal": 0.5557322218336633 - }, - "display_scores": { - "english": { - "astroturf": 0.0, - "fake_follower": 4.1, - "financial": 1.5, - "other": 4.7, - "overall": 4.7, - "self_declared": 3.2, - "spammer": 2.8 - }, - "universal": { - "astroturf": 0.3, - "fake_follower": 3.2, - "financial": 1.6, - "other": 3.8, - "overall": 3.8, - "self_declared": 3.7, - "spammer": 2.3 - } - }, - "raw_scores": { - "english": { - "astroturf": 0.0, - "fake_follower": 0.81, - "financial": 0.3, - "other": 0.94, - "overall": 0.94, - "self_declared": 0.63, - "spammer": 0.57 - }, - "universal": { - "astroturf": 0.06, - "fake_follower": 0.64, - "financial": 0.3133333333333333, - "other": 0.76, - "overall": 0.76, - "self_declared": 0.74, - "spammer": 0.47 - } - }, - "user": { - "majority_lang": "en", - "user_data": { - "id_str": "11330", - "screen_name": "test_screen_name" - } - } -} +bomx = botometer.BotometerX(rapidapi_key=rapidapi_key) ``` -Meanings of the elements in the response: - -* **user**: Twitter user object (from the user) plus the language inferred from majority of tweets -* **raw scores**: bot score in the [0,1] range, both using English (all features) and Universal (language-independent) features; in each case we have the overall score and the sub-scores for each bot class (see below for subclass names and definitions) -* **display scores**: same as raw scores, but in the [0,5] range -* **cap**: conditional probability that accounts with a score **equal to or greater than this** are automated; based on inferred language - -Meanings of the bot type scores: - -* `fake_follower`: bots purchased to increase follower counts -* `self_declared`: bots from botwiki.org -* `astroturf`: manually labeled political bots and accounts involved in follow trains that systematically delete content -* `spammer`: accounts labeled as spambots from several datasets -* `financial
`: bots that post using cashtags -* `other`: miscellaneous other bots obtained from manual annotation, user feedback, etc. - -For more information on the response object, consult the [API Overview](https://rapidapi.com/OSoMe/api/botometer-pro/details) on RapidAPI. - -### BotometerLite - -In September, 2020, the BotometerLite endpoint was added. It leverages a lightweighted model and allows detecting likely bots in bulk. -Before accessing it, please make sure you have subscribed to the ULTRA plan on RapidAPI. - -Unlike Botometer-V4, BotometerLite just needs the user profile information and the timestamp of when the information was collected to perform bot detection. -There are two modes for BotometerLite: non-Twitter mode and Twitter mode. - -If you have already collected at least one tweet for each account you want to check, you can use the non-Twitter mode. -In this mode, you only need a RapidAPI key. ```python -import botometer - -rapidapi_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -blt = botometer.BotometerLite(rapidapi_key=rapidapi_key) +# Check accounts by usernames, note that @ is optional +bomx.get_botscores_in_batch(usernames=['@OSoMe_IU', 'botometer']) -# Prepare a list of tweets from the users that you want to perform bot detection on. -# The list should contain no more than 100 tweets. -tweet_list = [tweet1, tweet2, ...] +# Check accounts by ids +bomx.get_botscores_in_batch(user_ids=[2451308594, 187521608]) -blt_scores = blt.check_accounts_from_tweets(tweet_list) +# Check accounts by both usernames and ids +bomx.get_botscores_in_batch(usernames=['@OSoMe_IU'], user_ids=[2451308594]) ``` -Result: +The queries will return results like those below: ```json [ - {"botscore": 0.65, "tweet_id": "1234", "user_id": 1111}, - {"botscore": 0.29, "tweet_id": "12345", "user_id": 2222} + { + "bot_score": 0.09, + "timestamp": "Sat, 27 May 2023 23:57:16 GMT", + "user_id": "2451308594", + "username": "Botometer" + }, + { + "bot_score": 0.21, + "timestamp": "Thu, 25 May 2023 22:54:53 GMT", + "user_id": "187521608", + "username": "OSoMe_IU" + } ] ``` +The response will be a list of JSON objects. +Meanings of the elements in the object: +- `bot_score`: The bot score, a float number between 0 and 1 (note that we rescale the score to 1 to 5 on the Botometer X website) +- `timestamp`: The time when the bot score was calculated +- `user_id`: ID of the account +- `username`: Username of the account -Note that the tweet_id is also included in case multiple tweets from the same user are passed to the API. +For more information on the API, consult the [API Overview](https://rapidapi.com/OSoMe/api/botometer-pro/details) on RapidAPI. -If you only have a set of user_ids or screen_names, you will have to use the Twitter mode. -In addition to the RapidAPI key, this mode also requires a valid Twitter APP key. -The package would first query the Twitter user lookup API to fetch the user profiles, then pass the data to the Botometer Pro API -for the bot scores. -```python -import botometer +## Installation instructions -rapidapi_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -twitter_app_auth = { - 'consumer_key': 'xxxxxxxx', - 'consumer_secret': 'xxxxxxxxxx', - 'access_token': 'xxxxxxxxx', - 'access_token_secret': 'xxxxxxxxxxx', - } - -blt_twitter = botometer.BotometerLite(rapidapi_key=rapidapi_key, **twitter_app_auth) - -# Prepare a list of screen_names you want to check. -# The list should contain no more than 100 screen_names; please remove the @ -screen_name_list = ['yang3kc', 'onurvarol', 'clayadavis'] -blt_scores = blt_twitter.check_accounts_from_screen_names(screen_name_list) - -# Prepare a list of user_ids you want to check. -# The list should contain no more than 100 user_ids. -user_id_list = [1133069780917850112, 77436536, 1548959833] -blt_scores = blt_twitter.check_accounts_from_user_ids(user_id_list) -``` - -Result: -```json -[ - {"botscore": 0.17, "tweet_id": null, "user_id": 1133069780917850112}, - {"botscore": 0.2, "tweet_id": null, "user_id": 77436536}, - {"botscore": 0.16, "tweet_id": null, "user_id": 1548959833} -] -``` - -The tweet_id is set to null in this mode. - -Note that in the non-Twitter mode, the returned scores reflect the status of the accounts when the tweets were collected. -In the Twitter mode, on the other hand, the scores reflect the status of the accounts when you run the code, just like the Botometer-V4 endpoint. - -## Install instructions This package is on PyPI so you can install it with pip: ``` $ pip install botometer ``` - ## Dependencies ### Python dependencies * [requests](http://docs.python-requests.org/en/latest/) -* [tweepy](https://github.com/tweepy/tweepy) - -Both of these dependencies are available via `pip`, so you can install both at once with - - pip install requests tweepy - - -## RapidAPI and Twitter Access Details - -### RapidAPI key - -Our API is served via [RapidAPI](//rapidapi.com). You must sign up -for a free account in order to obtain a RapidAPI secret key. The easiest way to -get your secret key is to visit -[our API endpoint page](https://rapidapi.com/OSoMe/api/botometer-pro/endpoints) -and look in the endpoint's header parametsrs for the "X-RapidAPI-Key" as shown below: - -![Screenshot of RapidAPI header parameters](/docs/rapidapi_key.png) - -### Twitter app -In order to access Twitter's API, one needs to have/create a [Twitter app](https://apps.twitter.com/). -Once you've created an app, the authentication info can be found in the "Keys and Access Tokens" tab of the app's properties: -![Screenshot of app "Keys and Access Tokens"](/docs/twitter_app_keys.png) -## Authentication -By default, Botometer uses **user authentication** when interacting with Twitter's API as it is the least restrictive and the ratelimit matches with Botometer's **Pro** plan: 180 requests per 15-minute window. -One can instead use Twitter's **application authentication** in order to take advantage of the higher ratelimit that matches our **Ultra** plan: 450 requests per 15-minute window. Do note the differences between user and app-only authentication found under the header "Twitter API Authentication Model" in [Twitter's docs on authentication](https://developer.twitter.com/en/docs/basics/authentication/overview/oauth). - -To use app-only auth, just omit the `access_token` and `access_token_secret` in the `Botometer` constructor. - -```python -import botometer - -rapidapi_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # now it's called rapidapi key -twitter_app_auth = { - 'consumer_key': 'xxxxxxxx', - 'consumer_secret': 'xxxxxxxxxx' - } -bom = botometer.Botometer(wait_on_ratelimit=True, - rapidapi_key=rapidapi_key, - **twitter_app_auth) -``` +The dependency should be installed automatically with pip. ## References -- ***Botometer v4:*** Mohsen Sayyadiharikandeh, Onur Varol, Kai-Cheng Yang, Alessandro Flammini, Filippo Menczer. "Detection of Novel Social Bots by Ensembles of Specialized Classifiers." [ArXiv](https://arxiv.org/abs/2006.06867) +- **Botometer X:** The scores are calculated using the BotometerLite model. + +- ***Botometer v4:*** Mohsen Sayyadiharikandeh, Onur Varol, Kai-Cheng Yang, Alessandro Flammini, Filippo Menczer. "Detection of Novel Social Bots by Ensembles of Specialized Classifiers." [DOI](https://doi.org/10.1145/3340531.3412698), [ArXiv](https://arxiv.org/abs/2006.06867) - ***BotometerLite:*** Yang, K.; Varol, O.; Hui, P.; and Menczer, F. "Scalable and Generalizable Social Bot Detection through Data Selection." AAAI (2020). [DOI](http://doi.org/10.1609/aaai.v34i01.5460), [ArXiv](https://arxiv.org/abs/1911.09179) @@ -303,8 +124,3 @@ bom = botometer.Botometer(wait_on_ratelimit=True, - Varol O., Davis C., Menczer, F., Flammini, A. "Feature Engineering for Social Bot Detection", Feature Engineering for Machine Learning and Data Analytics [Google Books](https://books.google.com/books?id=661SDwAAQBAJ&lpg=PA311&dq=info%3AsM983rg_yb8J%3Ascholar.google.com&lr&pg=PA311#v=onepage&q&f=false) - Ferrara, Emilio, Onur Varol, Clayton Davis, Filippo Menczer, and Alessandro Flammini. "The rise of social bots." Communications of the ACM 59, no. 7 (2016): 96-104. [DOI](https://doi.org/10.1145/2818717), [ArXiv](https://arxiv.org/abs/1407.5225) - - -```python - -``` diff --git a/botometer/__init__.py b/botometer/__init__.py index 488f813..095589d 100644 --- a/botometer/__init__.py +++ b/botometer/__init__.py @@ -1,67 +1,20 @@ -from __future__ import print_function -import time - import requests -from requests import ConnectionError, HTTPError, Timeout -import tweepy -from tweepy.error import RateLimitError, TweepError -import datetime - - -class NoTimelineError(ValueError): - def __init__(self, sn, *args, **kwargs): - msg = "user '%s' has no tweets in timeline" % sn - super(NoTimelineError, self).__init__(msg, *args, **kwargs) - -class Botometer(object): - _TWITTER_RL_MSG = 'Rate limit exceeded for Twitter API method' - - def __init__(self, - consumer_key, consumer_secret, - access_token=None, access_token_secret=None, - rapidapi_key=None, - tweepy_kwargs={}, - **kwargs): - self.consumer_key = consumer_key - self.consumer_secret = consumer_secret - self.access_token_key = self.access_token = access_token - self.access_token_secret = access_token_secret - self.wait_on_ratelimit = kwargs.get('wait_on_ratelimit', False) - - self.rapidapi_key = rapidapi_key or kwargs.get('mashape_key') - - if self.access_token_key is None or self.access_token_secret is None: - auth = tweepy.AppAuthHandler( - self.consumer_key, self.consumer_secret) - else: - auth = tweepy.OAuthHandler( - self.consumer_key, self.consumer_secret) - auth.set_access_token( - self.access_token_key, self.access_token_secret) - self.twitter_api = tweepy.API( - auth, - parser=tweepy.parsers.JSONParser(), - wait_on_rate_limit=self.wait_on_ratelimit, - **tweepy_kwargs) - - self.api_url = kwargs.get('botometer_api_url', - 'https://botometer-pro.p.rapidapi.com') - self.api_version = kwargs.get('botometer_api_version', 4) - - @classmethod - def create_from(cls, instance, **kwargs): - my_kwargs = vars(instance) - my_kwargs.update(kwargs) - return cls(**my_kwargs) +class BotometerBase(object): + def __init__(self, rapidapi_key, **kwargs): + self.rapidapi_key = rapidapi_key + self.api_url = kwargs.get( + "botometer_api_url", "https://botometer-pro.p.rapidapi.com" + ) def _add_rapidapi_header(self, kwargs): if self.rapidapi_key: - kwargs.setdefault('headers', {}).update({ - 'x-rapidapi-key': self.rapidapi_key - }) + kwargs.setdefault("headers", {}).update( + {"x-rapidapi-key": self.rapidapi_key} + ) + return kwargs def _bom_get(self, *args, **kwargs): @@ -72,218 +25,76 @@ def _bom_post(self, *args, **kwargs): self._add_rapidapi_header(kwargs) return requests.post(*args, **kwargs) - def _get_twitter_data(self, user, full_user_object=False): - try: - user_timeline = self.twitter_api.user_timeline( - user, - include_rts=True, - count=200, - ) - - except RateLimitError as e: - e.args = (self._TWITTER_RL_MSG, 'statuses/user_timeline') - raise e - - if user_timeline: - user_data = user_timeline[0]['user'] - else: - user_data = self.twitter_api.get_user(user) - screen_name = '@' + user_data['screen_name'] - - try: - search = self.twitter_api.search(screen_name, count=100) - except RateLimitError as e: - e.args = (self._TWITTER_RL_MSG, 'search/tweets') - raise e - - payload = { - 'mentions': search['statuses'], - 'timeline': user_timeline, - 'user': user_data, - } - - if not full_user_object: - payload['user'] = { - 'id_str': user_data['id_str'], - 'screen_name': user_data['screen_name'], - } - - return payload - - #################### ## Public methods ## #################### - def bom_api_path(self, method=''): - return '/'.join([ - self.api_url.rstrip('/'), - str(self.api_version), - method, - ]) - - - def check_account(self, user, full_user_object=False): - payload = self._get_twitter_data(user, - full_user_object=full_user_object) - if not payload['timeline']: - raise NoTimelineError(payload['user']) - - url = self.bom_api_path('check_account') - bom_resp = self._bom_post(url, json=payload) - bom_resp.raise_for_status() - classification = bom_resp.json() - - return classification - - - def check_accounts_in(self, accounts, full_user_object=False, - on_error=None, **kwargs): - - sub_instance = self.create_from(self, wait_on_ratelimit=True, - botometer_api_url=self.api_url) - max_retries = kwargs.get('retries', 3) - - for account in accounts: - for num_retries in range(max_retries + 1): - result = None - try: - result = sub_instance.check_account( - account, full_user_object=full_user_object) - except (TweepError, NoTimelineError) as e: - err_msg = '{}: {}'.format( - type(e).__name__, - getattr(e, 'msg', '') or getattr(e, 'reason', ''), - ) - result = {'error': err_msg} - except (ConnectionError, HTTPError, Timeout) as e: - if num_retries >= max_retries: - raise - else: - time.sleep(2 ** num_retries) - except Exception as e: - if num_retries >= max_retries: - if on_error: - on_error(account, e) - else: - raise - - if result is not None: - yield account, result - break + def bom_api_path(self, method=""): + return "/".join( + [ + self.api_url.rstrip("/"), + str(self.api_version), + method, + ] + ) -class BotometerLite(Botometer): +class BotometerX(BotometerBase): """ - Class to interact with the BotometerLite API. - The present class has two modes: the Twitter mode and the non-Twitter mode. + Class to interact with the Botometer X API endpoint. - Non-Twitter mode: - No Twitter APP key is required in this mode, i.e., only rapidapi_key is required. - The users provide a list of no more than 100 tweets that they want to perform - bot detection on. - Via the self.check_accounts_from_tweets method, the present class - queries the RapidAPI to fetch the botscores. - - Twitter mode: - A valid Tiwtter APP key is required in this mode, i.e., consumer_key, consumer_secret, - and rapidapi_key are required. - The users provide a list of no more than 100 user_ids or screen_names. - Via the self.check_accounts_from_user_ids or check_accounts_from_screen_names method, - the the present class first queries the Twitter user lookup API to fetch the user profiles, - then queries the RapidAPI to fetch the botscore. - - Lists of tweets/user_ids/screen_names with more than 100 elements would be truncated to 100. - The users are responsible to handle the exceptions. + Lists of user_ids and/or screen_names with more than 100 elements would be truncated to 100. + Users are responsible to handle the exceptions. """ + TWEETS_PER_REQUEST = 100 - def __init__( - self, - rapidapi_key=None, - consumer_key=None, - consumer_secret=None, - access_token=None, - access_token_secret=None, - **kwargs - ): - self.twitter_mode = False - lite_api_url = kwargs.get( - 'botometerlite_api_url', - 'https://botometer-pro.p.rapidapi.com' - ) - if consumer_key is None or consumer_secret is None: - # No Twitter mode: the users provide the tweets - self.api_url = lite_api_url - else: - # Twitter mode: use Twitter lookup to fetch user profile - self.twitter_mode = True - super(BotometerLite, self).__init__( - consumer_key, - consumer_secret, - access_token=access_token, - access_token_secret=access_token_secret, - botometer_api_url=lite_api_url, - wait_on_ratelimit=kwargs.get('wait_on_ratelimit', False) - ) - self.api_version = kwargs.get('botometerlite_api_version', 'litev1') - self.rapidapi_key = rapidapi_key or kwargs.get('mashape_key') + def __init__(self, rapidapi_key, **kwargs): + super(BotometerX, self).__init__(rapidapi_key, **kwargs) - def _get_utc_now(self): - now_time = datetime.datetime.now(datetime.timezone.utc) - return datetime.datetime.strftime( - now_time, '%a %b %d %H:%M:%S %z %Y' - ) + self.api_version = "botometer-x" - def _get_twitter_data(self, **kwargs): - try: - user_objs = self.twitter_api.lookup_users(**kwargs) - except RateLimitError as e: - e.args = (self._TWITTER_RL_MSG, 'users/lookup') - raise e - return user_objs + def _is_list_of_type(self, list_to_check, type_to_check): + if isinstance(list_to_check, list): + return all(isinstance(item, type_to_check) for item in list_to_check) + return False - def _check_accounts_twitter_mode(self, query, **kwargs): - assert self.twitter_mode, "Twitter app key missing" + def get_botscores_in_batch(self, user_ids=None, usernames=None): + """ + Get botscores based on a list of user_ids and/or screen_names. + There should be no more than 100 accounts in the query. + """ + # Assign default values if not provided + user_ids = [] if user_ids is None else user_ids + usernames = [] if usernames is None else usernames - user_objs = self._get_twitter_data(**query) - now_str = self._get_utc_now() + if not self._is_list_of_type(user_ids, int) and not self._is_list_of_type( + user_ids, str + ): + raise ValueError("user_ids must be a list of integers or strings") - dummy_tweets = [] - for user_obj in user_objs: - dummy_tweets.append({ - 'created_at': now_str, - 'user': user_obj - }) - return self.check_accounts_from_tweets(dummy_tweets) + if not self._is_list_of_type(usernames, str): + raise ValueError("usernames must be a list of strings") - def check_accounts_from_user_ids(self, user_ids, **kwargs): - """ - Check accounts based on a list of user_ids. - The list should contain no more than 100 elements. - A valid Twitter APP key is required. - """ - query = {"user_ids": list(user_ids)[:self.TWEETS_PER_REQUEST]} - return self._check_accounts_twitter_mode(query, **kwargs) + if len(user_ids) == 0 and len(usernames) == 0: + raise ValueError("Must provide either user_ids or usernames") - def check_accounts_from_screen_names(self, screen_names, **kwargs): - """ - Check accounts based on a list of screen_names (please remove the @). - The list should contain no more than 100 elements. - A valid Twitter APP key is required. - """ - query = {"screen_names": list(screen_names)[:self.TWEETS_PER_REQUEST]} - return self._check_accounts_twitter_mode(query, **kwargs) + N_BOTSCORES_PER_QUERY = 100 - def check_accounts_from_tweets(self, tweets): - """ - Check accounts based on a list of tweets. - The list should contain no more than 100 elements. - No Twitter APP key is required. - """ - url = self.bom_api_path('check_accounts_in_bulk') - bom_resp = self._bom_post(url, json=tweets) - bom_resp.raise_for_status() - classification = bom_resp.json() + # Will only query the first N_BOTS_PER_QUERY items + if len(user_ids) > N_BOTSCORES_PER_QUERY: + # We have enough user ids, so we will query the first N_BOTS_PER_QUERY and ignore the usernames + user_ids = user_ids[:N_BOTSCORES_PER_QUERY] + usernames = [] + else: + # We will query all the user ids plus N_BOTS_PER_QUERY - len(user_ids) usernames + usernames = usernames[: N_BOTSCORES_PER_QUERY - len(user_ids)] + payload = { + "user_ids": user_ids, + "usernames": usernames, + } - return classification + url = self.bom_api_path("get_botscores_in_batch") + bom_resp = self._bom_post(url, json=payload) + bom_resp.raise_for_status() + return bom_resp.json() diff --git a/setup.py b/setup.py index 630a706..a49ab1b 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,13 @@ from setuptools import setup -setup(name='botometer', - version='1.6', - description='Check Twitter accounts for bot behavior', - url='https://github.com/IUNetSci/botometer-python', - download_url='https://github.com/IUNetSci/botometer-python/archive/1.0.zip', - author='Clayton A Davis, Kai-Cheng Yang', - author_email='claydavi@indiana.edu,yangkc@iu.edu', - license='MIT', - packages=['botometer'], - install_requires=[ - 'requests', - 'tweepy >= 3.5.0', - ], - ) +setup( + name="botometer", + version="2.0.1", + description="Check Twitter accounts for bot behavior", + url="https://github.com/osome-iu/botometer-python", + author="Kai-Cheng Yang", + author_email="yang3kc@gmail.com", + license="MIT", + packages=["botometer"], + install_requires=["requests"], +)