diff --git a/src/controller/buffersController.py b/src/controller/buffersController.py index 4f33fa48..592b635e 100644 --- a/src/controller/buffersController.py +++ b/src/controller/buffersController.py @@ -174,10 +174,10 @@ class bufferController(object): media_ids = [] for i in attachments: photo = open(i["file"], "rb") - img = self.session.twitter.twitter.upload_media(media=photo) - self.session.twitter.twitter.create_metadata(media_id=img["media_id"], alt_text=dict(text=i["description"])) + img = self.session.twitter.upload_media(media=photo) + self.session.twitter.create_metadata(media_id=img["media_id"], alt_text=dict(text=i["description"])) media_ids.append(img["media_id"]) - self.session.twitter.twitter.update_status(status=text, media_ids=media_ids) + self.session.twitter.update_status(status=text, media_ids=media_ids) def save_positions(self): try: @@ -290,7 +290,7 @@ class baseBufferController(bufferController): if tweet.has_key("message"): message = tweet["message"] try: - tweet = self.session.twitter.twitter.show_status(id=tweet_id, include_ext_alt_text=True, tweet_mode="extended") + tweet = self.session.twitter.show_status(id=tweet_id, include_ext_alt_text=True, tweet_mode="extended") urls = utils.find_urls_in_text(tweet["full_text"]) for url in range(0, len(urls)): try: tweet["full_text"] = tweet["full_text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"]) @@ -304,7 +304,7 @@ class baseBufferController(bufferController): while l != False: tweetsList.append(tweet) try: - tweet = self.session.twitter.twitter.show_status(id=l, include_ext_alt_text=True, tweet_mode="extended") + tweet = self.session.twitter.show_status(id=l, include_ext_alt_text=True, tweet_mode="extended") urls = utils.find_urls_in_text(tweet["full_text"]) for url in range(0, len(urls)): try: tweet["full_text"] = tweet["full_text"].replace(urls[url], tweet["entities"]["urls"][url]["expanded_url"]) @@ -719,10 +719,10 @@ class baseBufferController(bufferController): if answer == widgetUtils.YES: try: if self.name == "direct_messages" or self.name == "sent_direct_messages": - self.session.twitter.twitter.destroy_direct_message(id=self.get_right_tweet()["id"]) + self.session.twitter.destroy_direct_message(id=self.get_right_tweet()["id"]) self.session.db[self.name]["items"].pop(index) else: - self.session.twitter.twitter.destroy_status(id=self.get_right_tweet()["id"]) + self.session.twitter.destroy_status(id=self.get_right_tweet()["id"]) self.session.db[self.name].pop(index) self.buffer.list.remove_item(index) # if index > 0: @@ -745,7 +745,7 @@ class baseBufferController(bufferController): def get_quoted_tweet(self, tweet): # try: - quoted_tweet = self.session.twitter.twitter.show_status(id=tweet["id"]) + quoted_tweet = self.session.twitter.show_status(id=tweet["id"]) urls = utils.find_urls_in_text(quoted_tweet["text"]) for url in range(0, len(urls)): try: quoted_tweet["text"] = quoted_tweet["text"].replace(urls[url], quoted_tweet["entities"]["urls"][url]["expanded_url"]) @@ -756,7 +756,7 @@ class baseBufferController(bufferController): l = tweets.is_long(quoted_tweet) id = tweets.get_id(l) # try: - original_tweet = self.session.twitter.twitter.show_status(id=id) + original_tweet = self.session.twitter.show_status(id=id) urls = utils.find_urls_in_text(original_tweet["text"]) for url in range(0, len(urls)): try: original_tweet["text"] = original_tweet["text"].replace(urls[url], original_tweet["entities"]["urls"][url]["expanded_url"]) @@ -879,7 +879,7 @@ class listBufferController(baseBufferController): def get_user_ids(self): next_cursor = -1 while(next_cursor): - users = self.session.twitter.twitter.get_list_members(list_id=self.list_id, cursor=next_cursor, include_entities=False, skip_status=True) + users = self.session.twitter.get_list_members(list_id=self.list_id, cursor=next_cursor, include_entities=False, skip_status=True) for i in users['users']: if i["id"] not in self.users: self.users.append(i["id"]) @@ -1408,7 +1408,7 @@ class conversationBufferController(searchBufferController): tweet = self.tweet while tweet["in_reply_to_status_id"] != None: try: - tweet = self.session.twitter.twitter.show_status(id=tweet["in_reply_to_status_id"], tweet_mode="extended") + tweet = self.session.twitter.show_status(id=tweet["in_reply_to_status_id"], tweet_mode="extended") except TwythonError as err: break self.statuses.insert(0, tweet) diff --git a/src/controller/listsController.py b/src/controller/listsController.py index c418af43..f3d60ee0 100644 --- a/src/controller/listsController.py +++ b/src/controller/listsController.py @@ -29,7 +29,7 @@ class listsController(object): return [compose.compose_list(item) for item in self.session.db["lists"]] def get_user_lists(self, user): - self.lists = self.session.twitter.twitter.show_lists(reverse=True, screen_name=user) + self.lists = self.session.twitter.show_lists(reverse=True, screen_name=user) return [compose.compose_list(item) for item in self.lists] def create_list(self, *args, **kwargs): @@ -43,7 +43,7 @@ class listsController(object): else: mode = "private" try: - new_list = self.session.twitter.twitter.create_list(name=name, description=description, mode=mode) + new_list = self.session.twitter.create_list(name=name, description=description, mode=mode) self.session.db["lists"].append(new_list) self.dialog.lista.insert_item(False, *compose.compose_list(new_list)) except TwythonError as e: @@ -63,7 +63,7 @@ class listsController(object): else: mode = "private" try: - self.session.twitter.twitter.update_list(list_id=list["id"], name=name, description=description, mode=mode) + self.session.twitter.update_list(list_id=list["id"], name=name, description=description, mode=mode) self.session.get_lists() self.dialog.populate_list(self.get_all_lists(), True) except TwythonError as e: @@ -75,7 +75,7 @@ class listsController(object): list = self.session.db["lists"][self.dialog.get_item()]["id"] if lists.remove_list() == widgetUtils.YES: try: - self.session.twitter.twitter.delete_list(list_id=list) + self.session.twitter.delete_list(list_id=list) self.session.db["lists"].pop(self.dialog.get_item()) self.dialog.lista.remove_item(self.dialog.get_item()) except TwythonError as e: @@ -90,7 +90,7 @@ class listsController(object): if self.dialog.lista.get_count() == 0: return list_id = self.lists[self.dialog.get_item()]["id"] try: - list = self.session.twitter.twitter.subscribe_to_list(list_id=list_id) + list = self.session.twitter.subscribe_to_list(list_id=list_id) item = utils.find_item(list["id"], self.session.db["lists"]) self.session.db["lists"].append(list) except TwythonError as e: @@ -100,7 +100,7 @@ class listsController(object): if self.dialog.lista.get_count() == 0: return list_id = self.lists[self.dialog.get_item()]["id"] try: - list = self.session.twitter.twitter.unsubscribe_from_list(list_id=list_id) + list = self.session.twitter.unsubscribe_from_list(list_id=list_id) self.session.db["lists"].remove(list) except TwythonError as e: output.speak("error %s: %s" % (e.status_code, e.msg)) diff --git a/src/controller/mainController.py b/src/controller/mainController.py index c1b5b86d..e89c5b15 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -553,7 +553,7 @@ class Controller(object): dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) if dlg.get_response() == widgetUtils.OK: try: - list = buff.session.twitter.twitter.add_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) + list = buff.session.twitter.add_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()]["id"], buff.session.db["lists"]) listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()]["name"].lower()), buff.session.db["user_name"]) if listBuffer != None: listBuffer.get_user_ids() @@ -581,7 +581,7 @@ class Controller(object): dlg.populate_list([compose.compose_list(item) for item in buff.session.db["lists"]]) if dlg.get_response() == widgetUtils.OK: try: - list = buff.session.twitter.twitter.delete_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) + list = buff.session.twitter.delete_list_member(list_id=buff.session.db["lists"][dlg.get_item()]["id"], screen_name=user) older_list = utils.find_item(buff.session.db["lists"][dlg.get_item()]["id"], buff.session.db["lists"]) listBuffer = self.search_buffer("%s-list" % (buff.session.db["lists"][dlg.get_item()]["name"].lower()), buff.session.db["user_name"]) if listBuffer != None: listBuffer.get_user_ids() @@ -781,7 +781,7 @@ class Controller(object): return else: id = buffer.get_tweet()["id"] - tweet = buffer.session.twitter.twitter.show_status(id=id, include_ext_alt_text=True, tweet_mode="extended") + tweet = buffer.session.twitter.show_status(id=id, include_ext_alt_text=True, tweet_mode="extended") if tweet["favorited"] == False: call_threaded(buffer.session.api_call, call_name="create_favorite", _sound="favourite.ogg", id=id) else: @@ -816,7 +816,7 @@ class Controller(object): users = utils.get_all_users(tweet, buff.session.db) dlg = dialogs.userSelection.selectUserDialog(users=users, default=default) if dlg.get_response() == widgetUtils.OK: - usr = utils.if_user_exists(buff.session.twitter.twitter, dlg.get_user()) + usr = utils.if_user_exists(buff.session.twitter, dlg.get_user()) if usr != None: if usr == dlg.get_user(): commonMessageDialogs.suspended_user() diff --git a/src/controller/trendingTopics.py b/src/controller/trendingTopics.py index 102aa227..7b181b9b 100644 --- a/src/controller/trendingTopics.py +++ b/src/controller/trendingTopics.py @@ -8,7 +8,7 @@ class trendingTopicsController(object): self.countries = {} self.cities = {} self.dialog = trends.trendingTopicsDialog() - self.information = session.twitter.twitter.get_available_trends() + self.information = session.twitter.get_available_trends() self.split_information() widgetUtils.connect_event(self.dialog.country, widgetUtils.RADIOBUTTON, self.get_places) widgetUtils.connect_event(self.dialog.city, widgetUtils.RADIOBUTTON, self.get_places) diff --git a/src/controller/user.py b/src/controller/user.py index ffa80eec..187febdd 100644 --- a/src/controller/user.py +++ b/src/controller/user.py @@ -41,9 +41,9 @@ class profileController(object): self.do_update() def get_data(self, screen_name): - self.data = self.session.twitter.twitter.show_user(screen_name=screen_name) + self.data = self.session.twitter.show_user(screen_name=screen_name) if screen_name != self.session.db["user_name"]: - self.friendship_status = self.session.twitter.twitter.show_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name) + self.friendship_status = self.session.twitter.show_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name) def fill_profile_fields(self): self.dialog.set_name(self.data["name"]) @@ -81,11 +81,11 @@ class profileController(object): url = self.dialog.get("url") if self.file != None: try: - self.session.twitter.twitter.update_profile_image(image=self.file) + self.session.twitter.update_profile_image(image=self.file) except TwythonError as e: output.speak(u"Error %s. %s" % (e.error_code, e.msg)) try: - self.session.twitter.twitter.update_profile(name=name, description=description, location=location, url=url) + self.session.twitter.update_profile(name=name, description=description, location=location, url=url) except TwythonError as e: output.speak(u"Error %s. %s" % (e.error_code, e.msg)) diff --git a/src/controller/userActionsController.py b/src/controller/userActionsController.py index 372becf3..1d0cc788 100644 --- a/src/controller/userActionsController.py +++ b/src/controller/userActionsController.py @@ -29,43 +29,43 @@ class userActionsController(object): def follow(self, user): try: - self.session.twitter.twitter.create_friendship(screen_name=user ) + self.session.twitter.create_friendship(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def unfollow(self, user): try: - id = self.session.twitter.twitter.destroy_friendship(screen_name=user ) + id = self.session.twitter.destroy_friendship(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def mute(self, user): try: - id = self.session.twitter.twitter.create_mute(screen_name=user ) + id = self.session.twitter.create_mute(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def unmute(self, user): try: - id = self.session.twitter.twitter.destroy_mute(screen_name=user ) + id = self.session.twitter.destroy_mute(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def report(self, user): try: - id = self.session.twitter.twitter.report_spam(screen_name=user ) + id = self.session.twitter.report_spam(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def block(self, user): try: - id = self.session.twitter.twitter.create_block(screen_name=user ) + id = self.session.twitter.create_block(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) def unblock(self, user): try: - id = self.session.twitter.twitter.destroy_block(screen_name=user ) + id = self.session.twitter.destroy_block(screen_name=user ) except TwythonError as err: output.speak("Error %s: %s" % (err.error_code, err.msg), True) diff --git a/src/sessions/twitter/client.py b/src/sessions/twitter/client.py deleted file mode 100644 index 2d6ee2f3..00000000 --- a/src/sessions/twitter/client.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -import random -import webbrowser -import logging -import config -from requests import certs -from twython import Twython, TwythonError -from keys import keyring - -log = logging.getLogger("sessionTwitter") - -class twitter(object): - - def login(self, user_key, user_secret, verify_credentials): - self.twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), user_key, user_secret) - if verify_credentials == True: - self.credentials = self.twitter.verify_credentials() - - def authorise(self): - twitter = Twython(keyring.get("api_key"), keyring.get("api_secret")) - self.auth = twitter.get_authentication_tokens(callback_url="oob") - webbrowser.open_new_tab(self.auth['auth_url']) - - def verify_authorisation(self, settings, pincode): - self.twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), self.auth['oauth_token'], self.auth['oauth_token_secret']) - final = self.twitter.get_authorized_tokens(pincode) - self.save_configuration(settings, final["oauth_token"], final["oauth_token_secret"]) - - def save_configuration(self, settings, user_key, user_secret): - settings["twitter"]["user_key"] = user_key - settings["twitter"]["user_secret"] = user_secret - settings.write() diff --git a/src/sessions/twitter/session.py b/src/sessions/twitter/session.py index 3f8a7f60..db0bc0e9 100644 --- a/src/sessions/twitter/session.py +++ b/src/sessions/twitter/session.py @@ -3,16 +3,17 @@ import os import time import logging +import webbrowser import wx import config import output import application from pubsub import pub -from twython import TwythonError, TwythonRateLimitError, TwythonAuthError +from twython import Twython, TwythonError, TwythonRateLimitError, TwythonAuthError from mysc.thread_utils import call_threaded from keys import keyring from sessions import base -from sessions.twitter import client, utils, compose +from sessions.twitter import utils, compose from sessions.twitter.long_tweets import tweets, twishort from wxUI import authorisationDialog @@ -100,7 +101,6 @@ class Session(base.baseSession): def __init__(self, *args, **kwargs): super(Session, self).__init__(*args, **kwargs) - self.twitter = client.twitter() self.reconnection_function_active = False self.counter = 0 self.lists = [] @@ -112,11 +112,13 @@ class Session(base.baseSession): if self.settings["twitter"]["user_key"] != None and self.settings["twitter"]["user_secret"] != None: try: log.debug("Logging in to twitter...") - self.twitter.login(self.settings["twitter"]["user_key"], self.settings["twitter"]["user_secret"], verify_credentials) + self.twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), self.settings["twitter"]["user_key"], self.settings["twitter"]["user_secret"]) + if verify_credentials == True: + self.credentials = self.twitter.verify_credentials() self.logged = True log.debug("Logged.") self.counter = 0 - except: + except IOError: log.error("The login attempt failed.") self.logged = False else: @@ -129,12 +131,22 @@ class Session(base.baseSession): if self.logged == True: raise Exceptions.AlreadyAuthorisedError("The authorisation process is not needed at this time.") else: - self.twitter.authorise() + twitter = Twython(keyring.get("api_key"), keyring.get("api_secret")) + self.auth = twitter.get_authentication_tokens(callback_url="oob") + webbrowser.open_new_tab(self.auth['auth_url']) self.authorisation_dialog = authorisationDialog() self.authorisation_dialog.cancel.Bind(wx.EVT_BUTTON, self.authorisation_cancelled) self.authorisation_dialog.ok.Bind(wx.EVT_BUTTON, self.authorisation_accepted) self.authorisation_dialog.ShowModal() + def verify_authorisation(self, pincode): + twitter = Twython(keyring.get("api_key"), keyring.get("api_secret"), self.auth['oauth_token'], self.auth['oauth_token_secret']) + final = twitter.get_authorized_tokens(pincode) + self.settings["twitter"]["user_key"] = final["oauth_token"] + self.settings["twitter"]["user_secret"] = final["oauth_token_secret"] + self.settings.write() + del self.auth + def authorisation_cancelled(self, *args, **kwargs): """ Destroy the authorization dialog. """ self.authorisation_dialog.Destroy() @@ -143,7 +155,7 @@ class Session(base.baseSession): def authorisation_accepted(self, *args, **kwargs): """ Gets the PIN code entered by user and validate it through Twitter.""" pincode = self.authorisation_dialog.text.GetValue() - self.twitter.verify_authorisation(self.settings, pincode) + self.verify_authorisation(pincode) self.authorisation_dialog.Destroy() def get_more_items(self, update_function, users=False, dm=False, name=None, *args, **kwargs): @@ -152,7 +164,7 @@ class Session(base.baseSession): users, dm bool: If any of these is set to True, the function will treat items as users or dm (they need different handling). name str: name of the database item to put new element in.""" results = [] - data = getattr(self.twitter.twitter, update_function)(*args, **kwargs) + data = getattr(self.twitter, update_function)(*args, **kwargs) if users == True: if type(data) == dict and data.has_key("next_cursor"): self.db[name]["cursor"] = data["next_cursor"] @@ -181,7 +193,7 @@ class Session(base.baseSession): output.speak(preexec_message, True) while finished==False and tries < 25: try: - val = getattr(self.twitter.twitter, call_name)(*args, **kwargs) + val = getattr(self.twitter, call_name)(*args, **kwargs) finished = True except TwythonError as e: output.speak(e.message) @@ -201,7 +213,7 @@ class Session(base.baseSession): def search(self, name, *args, **kwargs): """ Search in twitter, passing args and kwargs as arguments to the Twython function.""" - tl = self.twitter.twitter.search(*args, **kwargs) + tl = self.twitter.search(*args, **kwargs) tl["statuses"].reverse() return tl["statuses"] @@ -210,23 +222,23 @@ class Session(base.baseSession): """ Gets favourites for the authenticated user or a friend or follower. name str: Name for storage in the database. args and kwargs are passed directly to the Twython function.""" - tl = self.call_paged(self.twitter.twitter.get_favorites, *args, **kwargs) + tl = self.call_paged("get_favorites", *args, **kwargs) return self.order_buffer(name, tl) def call_paged(self, update_function, *args, **kwargs): """ Makes a call to the Twitter API methods several times. Useful for get methods. this function is needed for retrieving more than 200 items. - update_function str: The function to call. This function must be child of self.twitter.twitter + update_function str: The function to call. This function must be child of self.twitter args and kwargs are passed to update_function. returns a list with all items retrieved.""" max = 0 results = [] - data = getattr(self.twitter.twitter, update_function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) + data = getattr(self.twitter, update_function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) results.extend(data) for i in range(0, max): if i == 0: max_id = results[-1]["id"] else: max_id = results[0]["id"] - data = getattr(self.twitter.twitter, update_function)(max_id=max_id, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) + data = getattr(self.twitter, update_function)(max_id=max_id, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) results.extend(data) results.reverse() return results @@ -234,11 +246,11 @@ class Session(base.baseSession): # @_require_login def get_user_info(self): """ Retrieves some information required by TWBlue for setup.""" - f = self.twitter.twitter.get_account_settings() + f = self.twitter.get_account_settings() sn = f["screen_name"] self.settings["twitter"]["user_name"] = sn self.db["user_name"] = sn - self.db["user_id"] = self.twitter.twitter.show_user(screen_name=sn)["id_str"] + self.db["user_id"] = self.twitter.show_user(screen_name=sn)["id_str"] try: self.db["utc_offset"] = f["time_zone"]["utc_offset"] except KeyError: @@ -246,7 +258,7 @@ class Session(base.baseSession): # Get twitter's supported languages and save them in a global variable #so we won't call to this method once per session. if len(application.supported_languages) == 0: - application.supported_languages = self.twitter.twitter.get_supported_languages() + application.supported_languages = self.twitter.get_supported_languages() self.get_lists() self.get_muted_users() self.settings.write() @@ -254,12 +266,12 @@ class Session(base.baseSession): # @_require_login def get_lists(self): """ Gets the lists that the user is subscribed to and stores them in the database. Returns None.""" - self.db["lists"] = self.twitter.twitter.show_lists(reverse=True) + self.db["lists"] = self.twitter.show_lists(reverse=True) # @_require_login def get_muted_users(self): """ Gets muted users (oh really?).""" - self.db["muted_users"] = self.twitter.twitter.list_mute_ids()["ids"] + self.db["muted_users"] = self.twitter.list_mute_ids()["ids"] # @_require_login def get_stream(self, name, function, *args, **kwargs): @@ -294,9 +306,9 @@ class Session(base.baseSession): except KeyError: cursor = -1 if cursor != -1: - tl = getattr(self.twitter.twitter, function)(cursor=cursor, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) + tl = getattr(self.twitter, function)(cursor=cursor, count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) else: - tl = getattr(self.twitter.twitter, function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) + tl = getattr(self.twitter, function)(count=self.settings["general"]["max_tweets_per_call"], *args, **kwargs) tl[items].reverse() num = self.order_cursored_buffer(name, tl[items]) # Recently, Twitter's new endpoints have cursor if there are more results. @@ -312,7 +324,6 @@ class Session(base.baseSession): log.debug("Restarting connection after 5 minutes.") del self.twitter self.logged = False - self.twitter = client.twitter() self.login(False) self.counter = 0 @@ -387,7 +398,7 @@ class Session(base.baseSession): id str: User identifier, provided by Twitter. returns an user dict.""" if self.db.has_key("users") == False or self.db["users"].has_key(id) == False: - user = self.twitter.twitter.show_user(id=id) + user = self.twitter.show_user(id=id) self.db["users"][user["id_str"]] = user return user else: @@ -398,13 +409,13 @@ class Session(base.baseSession): screen_name str: User name, such as tw_blue2, provided by Twitter. returns an user ID.""" if self.db.has_key("users") == False: - user = utils.if_user_exists(self.twitter.twitter, screen_name) + user = utils.if_user_exists(self.twitter, screen_name) self.db["users"][user["id_str"]] = user return user["id_str"] else: for i in self.db["users"].keys(): if self.db["users"][i]["screen_name"] == screen_name: return self.db["users"][i]["id_str"] - user = utils.if_user_exists(self.twitter.twitter, screen_name) + user = utils.if_user_exists(self.twitter, screen_name) self.db["users"][user["id_str"]] = user return user["id_str"] \ No newline at end of file