clean up code, setup in ready-to-run state
This commit is contained in:
@@ -7,25 +7,39 @@ Twitter bot that tracks cross-company interactions between the non-JP branches o
|
|||||||
|
|
||||||
## `.env`
|
## `.env`
|
||||||
These need to be defined in a `.env` file at the project root (outside of `src`):
|
These need to be defined in a `.env` file at the project root (outside of `src`):
|
||||||
```
|
|
||||||
# Scweet (scraping)
|
|
||||||
SCWEET_EMAIL=
|
|
||||||
SCWEET_USERNAME=
|
|
||||||
SCWEET_PASSWORD=
|
|
||||||
|
|
||||||
# Twitter API bot keys (posting)
|
### Scraper Credentials
|
||||||
api_key=
|
To get around rate limitations imposed on users, we scrape with multiple accounts. Each account is defined in the file using the following format:
|
||||||
api_secret=
|
```
|
||||||
oauth1_access_token=
|
scraper_usernameX=twitter_username
|
||||||
oauth1_access_secret=
|
scraper_passwordX=twitter_password
|
||||||
bearer_token=
|
```
|
||||||
|
where `X` is a number starting from 0, increasing by 1 for each account added. For instance:
|
||||||
|
```
|
||||||
|
scraper_username0=
|
||||||
|
scraper_password0=
|
||||||
|
scraper_username1=
|
||||||
|
scraper_password1=
|
||||||
|
```
|
||||||
|
The first account (`scraper_username0` and `scraper_password0`) will be used to attempt scraping private accounts. Make sure this account follows any private accounts that you want to scrape!
|
||||||
|
### Twitter API Stuff
|
||||||
|
The following keys/tokens are used for the official API via `tweepy`. We mainly use these to just post tweets.
|
||||||
|
```
|
||||||
|
app_key=
|
||||||
|
app_secret=
|
||||||
|
user_token=
|
||||||
|
user_secret=
|
||||||
|
```
|
||||||
|
### Screenshot Cookie *(optional)*
|
||||||
|
This is the authentication token obtained from a browser when signed in on the Twitter website. It's only needed if you want to screenshot tweets from privated accounts. Make sure the token belongs to an account that follows desired private accounts! Maybe have it belong to `scraper_username0`?
|
||||||
|
```
|
||||||
|
web_auth_token=
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running modes
|
## Running modes
|
||||||
The bot may run in these modes:
|
The bot may run in these modes:
|
||||||
* Catch-up (`c`): intended to run only once, scan all accounts for cross-company tweets and post them. Terminate when done posting all.
|
* Pass no argument to run in listen mode, which scrapes all accounts in the *list* folder at an interval.
|
||||||
- use `--auto-listen` to switch to listen mode when finished
|
* Pass `--straight-to-queue` to process the queue first before attempting to scrape.
|
||||||
* Listen (`l`): listens for tweets from list, sharing it if it's cross-company
|
|
||||||
* Command-line (`cmd`): an interactive mode for manual control and debugging (drops into Python interpretor)
|
* Command-line (`cmd`): an interactive mode for manual control and debugging (drops into Python interpretor)
|
||||||
|
|
||||||
*Created for the spirit of entertainment and in the name of unity.* ❤
|
*Created for the spirit of entertainment and in the name of unity.* ❤
|
||||||
|
|||||||
+3
-3
@@ -15,7 +15,7 @@
|
|||||||
# --- [Ethyria] ---
|
# --- [Ethyria] ---
|
||||||
1437952405283426310 MillieParfait
|
1437952405283426310 MillieParfait
|
||||||
1437963160544284675 EnnaAlouette
|
1437963160544284675 EnnaAlouette
|
||||||
1437959162651156484 NinaKosaka
|
1437959162651156484 NinaKosaka p
|
||||||
1437961007029227520 ReimuEndou
|
1437961007029227520 ReimuEndou
|
||||||
|
|
||||||
# --- [Luxiem]---
|
# --- [Luxiem]---
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
# --- [Noctyx] ---
|
# --- [Noctyx] ---
|
||||||
1490867613915828224 alban_knox
|
1490867613915828224 alban_knox
|
||||||
1491195742123397124 uki_violeta
|
1491195742123397124 uki_violeta
|
||||||
1492604168145539072 Yugo_Asuma
|
1492604168145539072 Yugo_Asuma p
|
||||||
1493392149664219138 Fulgur_Ovid
|
1493392149664219138 Fulgur_Ovid
|
||||||
1493394108014292993 sonny_brisko
|
1493394108014292993 sonny_brisko
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
1589536631324692480 MelocoKyoran
|
1589536631324692480 MelocoKyoran
|
||||||
1589524401170833409 HexHaywire
|
1589524401170833409 HexHaywire
|
||||||
1589531775058968576 D_Dropscythe
|
1589531775058968576 D_Dropscythe
|
||||||
1589539582399348738 ZaionLanZa
|
1589539582399348738 ZaionLanZa p
|
||||||
1591995159901663232 KotokaTorahime
|
1591995159901663232 KotokaTorahime
|
||||||
1589791076709171201 Ver_Vermillion
|
1589791076709171201 Ver_Vermillion
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
possible combinations which involve a "target cross-tweeter" B
|
[scraper rate limitations]
|
||||||
|
50 searches/pages every 15 minutes
|
||||||
|
- max 20 tweets per search
|
||||||
|
|
||||||
|
[possible combinations which involve a "target cross-tweeter" B]
|
||||||
A retweets B
|
A retweets B
|
||||||
- B's tweet may have cross-mentions (B1, B2, etc.)
|
- B's tweet may have cross-mentions (B1, B2, etc.)
|
||||||
- rt_author_id=B; rt_mentions=B1,B2,...
|
- rt_author_id=B; rt_mentions=B1,B2,...
|
||||||
@@ -12,5 +15,13 @@ A quotes a tweet from B
|
|||||||
A quotes a tweet mentioning B
|
A quotes a tweet mentioning B
|
||||||
- quote_retweeted=...; rt_mentions=B...
|
- quote_retweeted=...; rt_mentions=B...
|
||||||
|
|
||||||
|
A replies to B
|
||||||
|
r = B
|
||||||
|
A replies to a tweet mentioning B
|
||||||
|
- r=...; rtm=B1,B2,...
|
||||||
|
|
||||||
-- NO --
|
-- NO --
|
||||||
A retweets a tweet that quotes a tweet mentioning B?
|
A retweets a tweet that quotes a tweet mentioning B?
|
||||||
|
|
||||||
|
[potential code change]
|
||||||
|
rtm --> tgm (target tweet's mentions)
|
||||||
@@ -18,6 +18,10 @@ class AccountPool:
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def use_index(self, idx):
|
||||||
|
self.__idx = idx
|
||||||
|
return self.current()
|
||||||
|
|
||||||
def current(self):
|
def current(self):
|
||||||
if 0 <= self.__idx < len(self.__accounts):
|
if 0 <= self.__idx < len(self.__accounts):
|
||||||
return self.__accounts[self.__idx]
|
return self.__accounts[self.__idx]
|
||||||
|
|||||||
+38
-32
@@ -17,13 +17,12 @@ from twapi import TwAPI
|
|||||||
import talenttweet as tt
|
import talenttweet as tt
|
||||||
import ttweetqueue as ttq
|
import ttweetqueue as ttq
|
||||||
|
|
||||||
PROGRAM_ARGS = None
|
safe_to_post_tweets = True
|
||||||
safe_to_post_tweets = False
|
|
||||||
errored = False
|
errored = False
|
||||||
|
|
||||||
# Returns a list of sorted and filtered TalentTweets (should
|
# Returns a list of sorted and filtered TalentTweets (should
|
||||||
# be equivalent to queue.txt)
|
# be equivalent to queue.txt)
|
||||||
async def get_cross_talent_tweets():
|
async def get_cross_tweets_online():
|
||||||
global safe_to_post_tweets
|
global safe_to_post_tweets
|
||||||
|
|
||||||
scraper = Scraper()
|
scraper = Scraper()
|
||||||
@@ -38,9 +37,9 @@ async def get_cross_talent_tweets():
|
|||||||
# tweets = get_user_tweets(talent_id, since_date=queue.finished_user_dates.get(talent_id, None))
|
# tweets = get_user_tweets(talent_id, since_date=queue.finished_user_dates.get(talent_id, None))
|
||||||
since_date = queue.finished_user_dates.get(talent_id, None)
|
since_date = queue.finished_user_dates.get(talent_id, None)
|
||||||
ttweets = scraper.get_cross_ttweets_from_user(talent_username, since_date=since_date)
|
ttweets = scraper.get_cross_ttweets_from_user(talent_username, since_date=since_date)
|
||||||
|
print(f'got {len(ttweets)} TalentTweets')
|
||||||
for ttweet in ttweets:
|
for ttweet in ttweets:
|
||||||
if ttweet.tweet_id not in queue.ttweets_dict \
|
if ttweet.tweet_id not in queue.finished_ttweets \
|
||||||
and ttweet.tweet_id not in queue.finished_ttweets \
|
|
||||||
and ttweet.is_cross_company():
|
and ttweet.is_cross_company():
|
||||||
queue.add_ttweet(ttweet)
|
queue.add_ttweet(ttweet)
|
||||||
except KeyboardInterrupt as e:
|
except KeyboardInterrupt as e:
|
||||||
@@ -68,9 +67,9 @@ async def get_cross_talent_tweets():
|
|||||||
# return False = errored or we posted at least one ttweet
|
# return False = errored or we posted at least one ttweet
|
||||||
# return True = we didn't post a single ttweet
|
# return True = we didn't post a single ttweet
|
||||||
async def process_queue() -> bool:
|
async def process_queue() -> bool:
|
||||||
global PROGRAM_ARGS
|
|
||||||
global errored
|
global errored
|
||||||
WAIT_TIME = 60*3
|
|
||||||
|
WAIT_TIME = 60*15
|
||||||
ttweets_posted = 0
|
ttweets_posted = 0
|
||||||
errored = False
|
errored = False
|
||||||
|
|
||||||
@@ -81,13 +80,10 @@ async def process_queue() -> bool:
|
|||||||
print('Posting queue is empty!')
|
print('Posting queue is empty!')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if PROGRAM_ARGS.announce_catchup:
|
|
||||||
TwAPI.instance.post_tweet(text=f'Starting to catch up through {queued_ttweets_count} logged tweets.')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while not queue.is_empty():
|
while not queue.is_empty():
|
||||||
ttweet = queue.get_next_ttweet()
|
ttweet = queue.get_next_ttweet()
|
||||||
tweet_was_successful = await TwAPI.instance.post_ttweet(ttweet, is_catchup=True)
|
tweet_was_successful = await TwAPI.instance.post_ttweet(ttweet)
|
||||||
|
|
||||||
print('running queue.good()...')
|
print('running queue.good()...')
|
||||||
queue.good()
|
queue.good()
|
||||||
@@ -103,9 +99,6 @@ async def process_queue() -> bool:
|
|||||||
print('Unhandled error occurred while posting tweets from queue.')
|
print('Unhandled error occurred while posting tweets from queue.')
|
||||||
errored = True
|
errored = True
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
|
||||||
if PROGRAM_ARGS.announce_catchup:
|
|
||||||
await TwAPI.instance.post_tweet('Finished with catch-up tweets!')
|
|
||||||
|
|
||||||
if errored or ttweets_posted > 0:
|
if errored or ttweets_posted > 0:
|
||||||
return False
|
return False
|
||||||
@@ -113,26 +106,39 @@ async def process_queue() -> bool:
|
|||||||
|
|
||||||
# return True = no problems
|
# return True = no problems
|
||||||
# return False = issue occurred where we couldn't post all past tweets properly
|
# return False = issue occurred where we couldn't post all past tweets properly
|
||||||
async def run():
|
async def run(PROGRAM_ARGS):
|
||||||
global errored
|
global errored
|
||||||
global safe_to_post_tweets
|
global safe_to_post_tweets
|
||||||
|
|
||||||
queue = ttq.TalentTweetQueue.instance
|
queue = ttq.TalentTweetQueue.instance
|
||||||
while True:
|
|
||||||
await get_cross_talent_tweets()
|
|
||||||
print(f'{queue.get_count()} cross-company tweets to attempt sharing.')
|
|
||||||
try:
|
|
||||||
if safe_to_post_tweets:
|
|
||||||
if await process_queue():
|
|
||||||
print('Posted no new tweets; we\'re caught up!')
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print('Tweets were not retrieved cleanly.')
|
|
||||||
return False
|
|
||||||
except:
|
|
||||||
print('Unhandled error occurred while running catch up in posting phase.')
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
if errored:
|
async def queue_loop():
|
||||||
return False
|
while True:
|
||||||
|
print(f'{queue.get_count()} cross-company tweets to attempt sharing.')
|
||||||
|
try:
|
||||||
|
if safe_to_post_tweets:
|
||||||
|
if await process_queue():
|
||||||
|
print('Posted no new tweets; we\'re caught up!')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print('Tweets were not retrieved cleanly.')
|
||||||
|
return False
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Interrupting queue processing...')
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
print('Unhandled error occurred while running catch up in posting phase.')
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
if errored:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await get_cross_tweets_online()
|
||||||
|
|
||||||
|
if PROGRAM_ARGS.straight_to_queue:
|
||||||
|
print('Processing queue first before pulling tweets...')
|
||||||
|
return await queue_loop()
|
||||||
|
else:
|
||||||
|
await get_cross_tweets_online()
|
||||||
|
return await queue_loop()
|
||||||
|
|||||||
+4
-4
@@ -9,13 +9,13 @@ import catchup
|
|||||||
|
|
||||||
errors_encountered = 0
|
errors_encountered = 0
|
||||||
|
|
||||||
def run():
|
def run(PROGRAM_ARGS):
|
||||||
global errors_encountered
|
global errors_encountered
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
asyncio.run(catchup.run())
|
asyncio.run(catchup.run(PROGRAM_ARGS))
|
||||||
print('Sleeping for 30 minutes...')
|
print('Sleeping for 10 minutes...')
|
||||||
sleep(1800) # run every half-hour
|
sleep(60*10) # run every 10 minutes
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('Interrupt signal received. Exiting listen mode.')
|
print('Interrupt signal received. Exiting listen mode.')
|
||||||
print(f'{errors_encountered} errors encountered throughout session.')
|
print(f'{errors_encountered} errors encountered throughout session.')
|
||||||
|
|||||||
+11
-24
@@ -15,44 +15,34 @@ from twapi import TwAPI
|
|||||||
PROGRAM_ARGS = None
|
PROGRAM_ARGS = None
|
||||||
|
|
||||||
MODES_HELP_STR = '''mode to run the bot at:
|
MODES_HELP_STR = '''mode to run the bot at:
|
||||||
l,listen: listen for new tweets from all accounts; will not terminate unless error occurs
|
<blank> scrape accounts in lists and post cross-company tweets if relevant
|
||||||
c,catchup: scan all tweets from all accounts; will terminate when done
|
cmd drop into Python interpretor with access to initialized variables'''
|
||||||
d,delete-all: delete all tweets on account provided by secrets.ini; make sure the function is uncommented in twapi.py'''
|
|
||||||
|
|
||||||
def init_argparse():
|
def init_argparse():
|
||||||
p = argparse.ArgumentParser(description='Twitter bot that follows interactions between Nijisanji EN/ID and hololive EN/ID members.', formatter_class=RawTextHelpFormatter)
|
p = argparse.ArgumentParser(description='Twitter bot that follows interactions between Nijisanji EN/ID and hololive EN/ID members.', formatter_class=RawTextHelpFormatter)
|
||||||
p.add_argument('mode', nargs='?', \
|
p.add_argument('mode', nargs='?', help=MODES_HELP_STR)
|
||||||
help=MODES_HELP_STR)
|
p.add_argument('--no-listen', action='store_true', help='Run one scraping-posting cycle without waiting to run again.')
|
||||||
p.add_argument('--no-delay', action='store_true', help='In self-destruct mode, clear tweets without safety waiting.')
|
p.add_argument('--straight-to-queue', action='store_true', help='Go through queue first before attempting to pull tweets.')
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def command_line():
|
def command_line():
|
||||||
# TODO (extra): implement command line mode for manually controlling the bot
|
# TODO (extra): implement command line mode for manually controlling the bot
|
||||||
print('Shell coming soon. For now, here\'s a Python interpretor.')
|
print('Here\'s a Python interpretor.')
|
||||||
code.interact(local=globals())
|
code.interact(local=globals())
|
||||||
pass
|
|
||||||
|
|
||||||
async def self_destruct():
|
|
||||||
if not PROGRAM_ARGS.no_delay:
|
|
||||||
print('\033[31;6m-----DELETING ALL TWEETS IN 10 SECONDS!! PRESS CTRL+C TO CANCEL.-----\033[0m')
|
|
||||||
await asyncio.sleep(10)
|
|
||||||
await TwAPI.instance.nuke_tweets()
|
|
||||||
|
|
||||||
async def async_main():
|
async def async_main():
|
||||||
global PROGRAM_ARGS
|
global PROGRAM_ARGS
|
||||||
|
|
||||||
if PROGRAM_ARGS.mode == None:
|
if PROGRAM_ARGS.mode == None:
|
||||||
await catchup.run()
|
if PROGRAM_ARGS.no_listen:
|
||||||
|
await catchup.run(PROGRAM_ARGS)
|
||||||
|
else:
|
||||||
|
listen.run(PROGRAM_ARGS)
|
||||||
return
|
return
|
||||||
|
|
||||||
mode = PROGRAM_ARGS.mode.lower()
|
mode = PROGRAM_ARGS.mode.lower()
|
||||||
if mode in ['d', 'delete-all']:
|
if mode == 'cmd':
|
||||||
print('WARNING: SELF-DESTRUCT MODE')
|
|
||||||
await self_destruct()
|
|
||||||
elif mode == 'cmd':
|
|
||||||
command_line()
|
command_line()
|
||||||
elif mode in ['l', 'listen']:
|
|
||||||
listen.run()
|
|
||||||
else:
|
else:
|
||||||
print('\nunknown mode. run with no arguments or -h for help and modes')
|
print('\nunknown mode. run with no arguments or -h for help and modes')
|
||||||
|
|
||||||
@@ -66,8 +56,6 @@ def main():
|
|||||||
|
|
||||||
PROGRAM_ARGS = parser.parse_args()
|
PROGRAM_ARGS = parser.parse_args()
|
||||||
|
|
||||||
## We expect to run in some mode now.
|
|
||||||
|
|
||||||
# Initialize shared API instance
|
# Initialize shared API instance
|
||||||
TwAPI()
|
TwAPI()
|
||||||
|
|
||||||
@@ -78,7 +66,6 @@ def main():
|
|||||||
ttq.TalentTweetQueue()
|
ttq.TalentTweetQueue()
|
||||||
|
|
||||||
## Asynchronous execution
|
## Asynchronous execution
|
||||||
print('beginning async main')
|
|
||||||
nest_asyncio.apply()
|
nest_asyncio.apply()
|
||||||
asyncio.run(async_main())
|
asyncio.run(async_main())
|
||||||
|
|
||||||
|
|||||||
+23
-9
@@ -20,8 +20,12 @@ class Scraper:
|
|||||||
self.__account = AccountPool()
|
self.__account = AccountPool()
|
||||||
self.try_login()
|
self.try_login()
|
||||||
|
|
||||||
def try_login(self) -> bool:
|
def try_login(self, account_idx: int = None) -> bool:
|
||||||
acc = self.__account.next()
|
if account_idx is not None:
|
||||||
|
acc = self.__account.use_index(account_idx)
|
||||||
|
else:
|
||||||
|
acc = self.__account.next()
|
||||||
|
|
||||||
if acc is not None:
|
if acc is not None:
|
||||||
name = acc[0]
|
name = acc[0]
|
||||||
print(f"using {name}")
|
print(f"using {name}")
|
||||||
@@ -65,9 +69,10 @@ class Scraper:
|
|||||||
# recover lost info
|
# recover lost info
|
||||||
if tweet.is_retweet:
|
if tweet.is_retweet:
|
||||||
if tweet.retweeted_tweet is None:
|
if tweet.retweeted_tweet is None:
|
||||||
print(f'{tweet.author.username}/{tweet.id} is missing the RT! Recovering...')
|
print(f'{tweet.author.username}/{tweet.id} is missing the RT! It\'s probably nothing...')
|
||||||
tweet.retweeted_tweet = self.app.tweet_detail(str(tweet.id)).retweeted_tweet
|
# tweet.retweeted_tweet = self.app.tweet_detail(str(tweet.id)).retweeted_tweet
|
||||||
if tweet.retweeted_tweet.author is None:
|
tweet.is_retweet = False
|
||||||
|
elif tweet.retweeted_tweet.author is None:
|
||||||
print(f'WARNING: {tweet.author.username}/{tweet.id} is missing the RT author! Recovering details...')
|
print(f'WARNING: {tweet.author.username}/{tweet.id} is missing the RT author! Recovering details...')
|
||||||
tweet.retweeted_tweet = self.app.tweet_detail(tweet.retweeted_tweet.id)
|
tweet.retweeted_tweet = self.app.tweet_detail(tweet.retweeted_tweet.id)
|
||||||
|
|
||||||
@@ -78,17 +83,20 @@ class Scraper:
|
|||||||
tweet.is_quoted = False
|
tweet.is_quoted = False
|
||||||
elif tweet.quoted_tweet.author is None:
|
elif tweet.quoted_tweet.author is None:
|
||||||
print(f'WARNING: {tweet.author.username}/{tweet.id} is missing the QRT author! Recovering details...')
|
print(f'WARNING: {tweet.author.username}/{tweet.id} is missing the QRT author! Recovering details...')
|
||||||
tweet.quoted_tweet= self.app.tweet_detail(tweet.quoted_tweet.id)
|
tweet.quoted_tweet = self.app.tweet_detail(tweet.quoted_tweet.id)
|
||||||
|
|
||||||
# fix reply if it exists
|
# fix reply if it exists
|
||||||
# if tweet.is_reply and tweet.replied_to is None:
|
# if tweet.is_reply and tweet.replied_to is None:
|
||||||
# tweet.replied_to = self.app.tweet_detail(tweet._original_tweet['in_reply_to_status_id_str'])
|
# tweet.replied_to = self.app.tweet_detail(tweet.original_tweet['in_reply_to_status_id_str'])
|
||||||
tweets.append(tweet)
|
tweets.append(tweet)
|
||||||
|
|
||||||
if not reached_backdate and int(tweet.author.id) == uid and tweet.date <= since:
|
if not reached_backdate and int(tweet.author.id) == uid and tweet.date <= since:
|
||||||
print("reached backdate")
|
print("reached backdate")
|
||||||
reached_backdate = True
|
reached_backdate = True
|
||||||
|
|
||||||
|
if uid in talent_lists.privated_accounts:
|
||||||
|
self.try_login(0)
|
||||||
|
|
||||||
while not reached_backdate:
|
while not reached_backdate:
|
||||||
try:
|
try:
|
||||||
# uts = self.app.get_tweets(uid, replies=True, cursor=cur)
|
# uts = self.app.get_tweets(uid, replies=True, cursor=cur)
|
||||||
@@ -110,8 +118,14 @@ class Scraper:
|
|||||||
cur = search.cursor
|
cur = search.cursor
|
||||||
except UnknownError:
|
except UnknownError:
|
||||||
print("UnknownError occurred, probably rate-limited")
|
print("UnknownError occurred, probably rate-limited")
|
||||||
# traceback.print_exc()
|
if uid in talent_lists.privated_accounts:
|
||||||
if not self.try_login():
|
print("sticking pvt-accessible account. sleeping for 2 minutes...")
|
||||||
|
sleep(120)
|
||||||
|
print()
|
||||||
|
l = self.try_login(0)
|
||||||
|
else:
|
||||||
|
l = self.try_login()
|
||||||
|
if not l:
|
||||||
print("sleeping for 2 minutes...")
|
print("sleeping for 2 minutes...")
|
||||||
sleep(120)
|
sleep(120)
|
||||||
print()
|
print()
|
||||||
|
|||||||
+10
-6
@@ -6,6 +6,7 @@ niji_en: dict[int, str] = dict()
|
|||||||
niji_exid: dict[int, str] = dict()
|
niji_exid: dict[int, str] = dict()
|
||||||
talents: dict[int, str] = dict()
|
talents: dict[int, str] = dict()
|
||||||
talents_company: dict[int, str] = dict()
|
talents_company: dict[int, str] = dict()
|
||||||
|
privated_accounts: dict[int, str] = dict()
|
||||||
|
|
||||||
test_talents = dict()
|
test_talents = dict()
|
||||||
|
|
||||||
@@ -16,12 +17,15 @@ def __create_dict(file, _dict, company):
|
|||||||
with open(file, 'r') as f:
|
with open(file, 'r') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
words = line.split()
|
words = line.split()
|
||||||
if len(words) == 2 and line[0] != '#':
|
if len(words) >= 2 and line[0] != '#':
|
||||||
id, name = line.split()
|
t = line.split()
|
||||||
|
id, name = int(t[0]), t[1]
|
||||||
# name = f'{util.get_username_online(id, default=name)}' # attempt to get updated name
|
# name = f'{util.get_username_online(id, default=name)}' # attempt to get updated name
|
||||||
talents[int(id)] = name
|
talents[id] = name
|
||||||
_dict[int(id)] = name
|
_dict[id] = name
|
||||||
talents_company[int(id)] = company
|
talents_company[id] = company
|
||||||
|
if len(words) > 2 and words[2] == 'p':
|
||||||
|
privated_accounts[id] = name
|
||||||
def init():
|
def init():
|
||||||
global holo_en
|
global holo_en
|
||||||
global holo_id
|
global holo_id
|
||||||
@@ -36,7 +40,7 @@ def init():
|
|||||||
# nijiEN
|
# nijiEN
|
||||||
__create_dict(f'{util.get_project_dir()}/lists/nijien.txt', niji_en, 'nijiEN')
|
__create_dict(f'{util.get_project_dir()}/lists/nijien.txt', niji_en, 'nijiEN')
|
||||||
# nijiexID
|
# nijiexID
|
||||||
__create_dict(f'{util.get_project_dir()}/lists/nijiexid.txt', niji_exid, 'nijiex-ID')
|
__create_dict(f'{util.get_project_dir()}/lists/nijiexid.txt', niji_exid, 'nijiex\'ID')
|
||||||
# TODO: nijiex-KR
|
# TODO: nijiex-KR
|
||||||
|
|
||||||
test_talents = holo_en
|
test_talents = holo_en
|
||||||
|
|||||||
+44
-29
@@ -5,7 +5,8 @@ import platform
|
|||||||
import pytz
|
import pytz
|
||||||
from tweety.types import *
|
from tweety.types import *
|
||||||
|
|
||||||
from talent_lists import is_cross_company
|
# from talent_lists import is_cross_company, talents
|
||||||
|
import talent_lists as tl
|
||||||
import util
|
import util
|
||||||
|
|
||||||
class TalentTweet:
|
class TalentTweet:
|
||||||
@@ -94,7 +95,7 @@ class TalentTweet:
|
|||||||
date_time=tweety.date, text=tweety.text,
|
date_time=tweety.date, text=tweety.text,
|
||||||
mrq=(
|
mrq=(
|
||||||
[int(x.id) for x in tweety.user_mentions],
|
[int(x.id) for x in tweety.user_mentions],
|
||||||
int(tweety._original_tweet['in_reply_to_user_id_str']) if tweety.is_reply else None,
|
int(tweety.original_tweet['in_reply_to_user_id_str']) if tweety.is_reply else None,
|
||||||
int(tweety.quoted_tweet.author.id) if tweety.quoted_tweet is not None else None
|
int(tweety.quoted_tweet.author.id) if tweety.quoted_tweet is not None else None
|
||||||
),
|
),
|
||||||
rt_author_id=tweety.retweeted_tweet.author.id if tweety.is_retweet else None,
|
rt_author_id=tweety.retweeted_tweet.author.id if tweety.is_retweet else None,
|
||||||
@@ -108,15 +109,22 @@ class TalentTweet:
|
|||||||
self.date_time = date_time
|
self.date_time = date_time
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
# filter twitter users to only be cross-company
|
# filter users to only be talents
|
||||||
self.mentions = {x for x in mrq[0] if is_cross_company(author_id, x)}
|
self.mentions = {x for x in mrq[0] if x in tl.talents}
|
||||||
self.reply_to = mrq[1] if mrq[1] is not None and is_cross_company(author_id, mrq[1]) else None
|
self.rt_mentions = {x for x in rt_mentions if x in tl.talents}
|
||||||
self.quote_tweeted = mrq[2]
|
|
||||||
|
|
||||||
# rt'd/quoted tweet contains cross-company names?
|
self.reply_to = mrq[1]
|
||||||
self.rt_mentions = {x for x in rt_mentions if is_cross_company(author_id, x)}
|
self.quote_tweeted = mrq[2]
|
||||||
self.rt_author_id = rt_author_id
|
self.rt_author_id = rt_author_id
|
||||||
|
|
||||||
|
try: self.mentions.remove(self.reply_to)
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
# -1 if user is not in company
|
||||||
|
self.reply_to = self.reply_to if self.reply_to is None or self.reply_to in tl.talents else -1
|
||||||
|
self.quote_tweeted = self.quote_tweeted if self.quote_tweeted is None or self.quote_tweeted in tl.talents else -1
|
||||||
|
self.rt_author_id = self.rt_author_id if self.rt_author_id is None or self.rt_author_id in tl.talents else -1
|
||||||
|
|
||||||
# all users involved except for the author
|
# all users involved except for the author
|
||||||
self.all_parties = {self.reply_to, self.quote_tweeted, rt_author_id}
|
self.all_parties = {self.reply_to, self.quote_tweeted, rt_author_id}
|
||||||
self.all_parties.update(self.mentions, self.rt_mentions)
|
self.all_parties.update(self.mentions, self.rt_mentions)
|
||||||
@@ -125,10 +133,6 @@ class TalentTweet:
|
|||||||
try: self.all_parties.remove(self.author_id)
|
try: self.all_parties.remove(self.author_id)
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
# clean up mentions
|
|
||||||
try: self.mentions.remove(self.reply_to)
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@@ -146,11 +150,11 @@ class TalentTweet:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def url(self):
|
def url(self):
|
||||||
return f'https://www.twitter.com/{self.username}/status/{self.tweet_id}'
|
return util.get_tweet_url(self.tweet_id, self.username)
|
||||||
|
|
||||||
def is_cross_company(self):
|
def is_cross_company(self):
|
||||||
for other_id in self.all_parties:
|
for other_id in self.all_parties:
|
||||||
if is_cross_company(self.author_id, other_id):
|
if tl.is_cross_company(self.author_id, other_id):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -167,14 +171,15 @@ class TalentTweet:
|
|||||||
unpad = '#' if platform.system() == 'Windows' else '-'
|
unpad = '#' if platform.system() == 'Windows' else '-'
|
||||||
return self.date_time.strftime(f'%b %{unpad}d %Y, %{unpad}I:%M%p (%Z)')
|
return self.date_time.strftime(f'%b %{unpad}d %Y, %{unpad}I:%M%p (%Z)')
|
||||||
|
|
||||||
def announce_text(self, is_catchup=False):
|
def announce_text(self):
|
||||||
# templates
|
# templates
|
||||||
REPLY = '{0} replied to {1}!'
|
|
||||||
TWEET = '{0} tweeted mentioning {1}!'
|
TWEET = '{0} tweeted mentioning {1}!'
|
||||||
|
REPLY = '{0} replied to {1}!'
|
||||||
|
REPLY_TO_MENTION_B = '{0} replied to a tweet{1}mentioning {1}!' #########################
|
||||||
RETWEET = '{0} retweeted {1}!'
|
RETWEET = '{0} retweeted {1}!'
|
||||||
RETWEET_MENTIONS_B = '{0} shared a tweet mentioning {1}!'
|
RETWEET_MENTIONS_B = '{0} shared a tweet{1}mentioning {2}!' #########################
|
||||||
QUOTE_TWEET = '{0} quote tweeted {1}!'
|
QUOTE_TWEET = '{0} quote tweeted {1}!'
|
||||||
QUOTE_TWEET_MENTIONS_B = '{0} quoted a tweet mentioning {1}!'
|
QUOTED_TWEET_MENTIONS_B = '{0} quoted a tweet{1}mentioning {2}!' #########################
|
||||||
|
|
||||||
author_username = f'@/{util.get_username_with_company(self.author_id)}'
|
author_username = f'@/{util.get_username_with_company(self.author_id)}'
|
||||||
ret = str()
|
ret = str()
|
||||||
@@ -184,28 +189,37 @@ class TalentTweet:
|
|||||||
except: pass
|
except: pass
|
||||||
mention_usernames = [f'@/{util.get_username_with_company(x)}' for x in print_mention_ids]
|
mention_usernames = [f'@/{util.get_username_with_company(x)}' for x in print_mention_ids]
|
||||||
|
|
||||||
if is_catchup:
|
def rtm_msg(TEMPLATE: str, rtm_author_username: str):
|
||||||
ret += f'{self.get_datetime_str()}\n'
|
if self.rt_author_id != -1: # rtm tweet is not from talent; rtm should be everyone
|
||||||
pass
|
rtm_names = [f'@/{util.get_username_with_company(x)}' for x in self.rt_mentions]
|
||||||
|
between = f' from {rtm_author_username} '
|
||||||
|
ret += TEMPLATE.format(author_username, between, ", ".join(rtm_names))
|
||||||
|
else: # rtm tweet is from a talent; rtm should just be cross company
|
||||||
|
rtm_names = [f'@/{util.get_username_with_company(x)}' for x in self.rt_mentions if tl.is_cross_company(self.author_id, x)]
|
||||||
|
ret += TEMPLATE.format(author_username, ' ', ", ".join(rtm_names))
|
||||||
|
|
||||||
rt_mention_names = [util.get_username_with_company(x) for x in self.rt_mentions]
|
|
||||||
# Tweet types
|
# Tweet types
|
||||||
if self.rt_author_id is not None: # retweet
|
if self.rt_author_id is not None: # retweet
|
||||||
|
rt_username = f'@/{util.get_username_with_company(self.rt_author_id)}' if self.rt_author_id != -1 else None
|
||||||
if len(self.rt_mentions) > 0:
|
if len(self.rt_mentions) > 0:
|
||||||
ret += RETWEET_MENTIONS_B.format(author_username, ", ".join(rt_mention_names))
|
rtm_msg(RETWEET_MENTIONS_B, rt_username)
|
||||||
else:
|
else:
|
||||||
ret += RETWEET.format(f'{author_username}', f'@/{util.get_username_with_company(self.rt_author_id)}')
|
ret += RETWEET.format(author_username, rt_username)
|
||||||
elif self.reply_to is not None: # reply
|
elif self.reply_to is not None: # reply
|
||||||
reply_username = f'@/{util.get_username_with_company(self.reply_to)}'
|
reply_username = f'@/{util.get_username_with_company(self.reply_to)}' if self.reply_to != -1 else None
|
||||||
ret += REPLY.format(author_username, reply_username)
|
|
||||||
elif self.quote_tweeted is not None: # qrt
|
|
||||||
quoted_username = f'@/{util.get_username_with_company(self.quote_tweeted)}'
|
|
||||||
if len(self.rt_mentions) > 0:
|
if len(self.rt_mentions) > 0:
|
||||||
ret += QUOTE_TWEET_MENTIONS_B.format(author_username, ", ".join(rt_mention_names))
|
rtm_msg(REPLY_TO_MENTION_B, reply_username)
|
||||||
|
else:
|
||||||
|
ret += REPLY.format(author_username, reply_username)
|
||||||
|
elif self.quote_tweeted is not None: # qrt
|
||||||
|
quoted_username = f'@/{util.get_username_with_company(self.quote_tweeted)}' if self.quote_tweeted != -1 else None
|
||||||
|
if len(self.rt_mentions) > 0:
|
||||||
|
rtm_msg(QUOTED_TWEET_MENTIONS_B, quoted_username)
|
||||||
else:
|
else:
|
||||||
ret += QUOTE_TWEET.format(author_username, quoted_username)
|
ret += QUOTE_TWEET.format(author_username, quoted_username)
|
||||||
elif len(self.mentions) > 0: # standalone tweet
|
elif len(self.mentions) > 0: # standalone tweet
|
||||||
ret += TWEET.format(author_username, ", ".join(mention_usernames))
|
ret += TWEET.format(author_username, ", ".join(mention_usernames))
|
||||||
|
f'[{self.get_datetime_str()}]\n'
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'TalentTweet {self.tweet_id} has insufficient other parties')
|
raise ValueError(f'TalentTweet {self.tweet_id} has insufficient other parties')
|
||||||
@@ -217,4 +231,5 @@ class TalentTweet:
|
|||||||
f'{", ".join(mention_usernames)}'
|
f'{", ".join(mention_usernames)}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ret += f'\n\n{self.get_datetime_str()}'
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
+7
-2
@@ -74,8 +74,8 @@ class TalentTweetQueue:
|
|||||||
return self.get_count() <= 0
|
return self.get_count() <= 0
|
||||||
|
|
||||||
def add_ttweet(self, ttweet):
|
def add_ttweet(self, ttweet):
|
||||||
self.__sorted = False
|
|
||||||
self.ttweets_dict[ttweet.tweet_id] = ttweet
|
self.ttweets_dict[ttweet.tweet_id] = ttweet
|
||||||
|
self.__sorted = False
|
||||||
|
|
||||||
def get_ttweet(self, id):
|
def get_ttweet(self, id):
|
||||||
return self.ttweets_dict[id]
|
return self.ttweets_dict[id]
|
||||||
@@ -84,7 +84,10 @@ class TalentTweetQueue:
|
|||||||
self.is_good = False
|
self.is_good = False
|
||||||
if os.path.exists(self.current_ttweet_path):
|
if os.path.exists(self.current_ttweet_path):
|
||||||
with open(self.current_ttweet_path, 'r') as f:
|
with open(self.current_ttweet_path, 'r') as f:
|
||||||
return tt.TalentTweet.deserialize(f.readline())
|
ttweet = tt.TalentTweet.deserialize(f.readline())
|
||||||
|
if ttweet.tweet_id in self.ttweets_dict:
|
||||||
|
self.ttweets_dict.pop(ttweet.tweet_id)
|
||||||
|
return ttweet
|
||||||
|
|
||||||
self.__sort_ttweets_dict()
|
self.__sort_ttweets_dict()
|
||||||
key = list(self.ttweets_dict.keys())[0]
|
key = list(self.ttweets_dict.keys())[0]
|
||||||
@@ -109,6 +112,7 @@ class TalentTweetQueue:
|
|||||||
|
|
||||||
# overwrite queue.txt
|
# overwrite queue.txt
|
||||||
def save_file(self):
|
def save_file(self):
|
||||||
|
print('saving file...', end='')
|
||||||
shutil.copyfile(self.queue_path, self.queue_backup_path)
|
shutil.copyfile(self.queue_path, self.queue_backup_path)
|
||||||
self.__sort_ttweets_dict()
|
self.__sort_ttweets_dict()
|
||||||
with open(self.queue_path, 'w') as f:
|
with open(self.queue_path, 'w') as f:
|
||||||
@@ -121,6 +125,7 @@ class TalentTweetQueue:
|
|||||||
# write sorted ttweets
|
# write sorted ttweets
|
||||||
for ttweet in self.ttweets_dict.values():
|
for ttweet in self.ttweets_dict.values():
|
||||||
f.write(ttweet.serialize() + '\n')
|
f.write(ttweet.serialize() + '\n')
|
||||||
|
print('done')
|
||||||
|
|
||||||
def add_finished_tweet(self, id):
|
def add_finished_tweet(self, id):
|
||||||
self.finished_ttweets.append(id)
|
self.finished_ttweets.append(id)
|
||||||
|
|||||||
+12
-51
@@ -76,54 +76,18 @@ class TwAPI:
|
|||||||
consumer_key=creds['app_key'], consumer_secret=creds['app_secret'],
|
consumer_key=creds['app_key'], consumer_secret=creds['app_secret'],
|
||||||
access_token=creds['user_token'], access_token_secret=creds['user_secret']
|
access_token=creds['user_token'], access_token_secret=creds['user_secret']
|
||||||
)
|
)
|
||||||
# self.api = tweepy.API(
|
self.api = tweepy.API(
|
||||||
# auth=tweepy.OAuthHandler(
|
auth=tweepy.OAuthHandler(
|
||||||
# consumer_key=api_secrets.api_key(), consumer_secret=api_secrets.api_secret(),
|
consumer_key=creds['app_key'], consumer_secret=creds['app_secret'],
|
||||||
# access_token=api_secrets.access_token(), access_token_secret=api_secrets.access_secret()
|
access_token=creds['user_token'], access_token_secret=creds['user_secret']
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
|
|
||||||
# try:
|
try:
|
||||||
# self.me = self.client.get_me(wait_on_rate_limit=True).data
|
self.me = self.client.get_me().data
|
||||||
# except Exception as e:
|
print(f'Assuming the account of @{self.me.data["username"]} ({self.me["id"]})')
|
||||||
# print('Failed to login!')
|
except:
|
||||||
# raise e
|
pass
|
||||||
# print(f'Assuming the account of @{self.me.data["username"]} ({self.me["id"]})')
|
|
||||||
|
|
||||||
## ---[COMMENT OUT WHEN NOT IN USE]---
|
|
||||||
# async def nuke_tweets(self):
|
|
||||||
# async def delete_tweet(id):
|
|
||||||
# try:
|
|
||||||
# self.client.delete_tweet(id)
|
|
||||||
# except tweepy.TooManyRequests as e:
|
|
||||||
# wait_for = float(e.response.headers["x-rate-limit-reset"]) - datetime.datetime.now().timestamp() + 1
|
|
||||||
# print(f'\thit rate limit deleting {id}, retrying in {wait_for} seconds...')
|
|
||||||
# await asyncio.sleep(wait_for)
|
|
||||||
# print('continuing...')
|
|
||||||
# await delete_tweet(id)
|
|
||||||
|
|
||||||
# print(f'Retrieving all of {self.me["username"]}\'s tweets...')
|
|
||||||
# tweets = self.get_all_tweet_ids_from_user(self.me['id'])
|
|
||||||
|
|
||||||
# print(f'Retrieved {len(tweets)} tweets.')
|
|
||||||
# if not len(tweets) > 0:
|
|
||||||
# print('No tweets obtained. Make sure the profile is public.')
|
|
||||||
# return
|
|
||||||
|
|
||||||
# print(f'Deleting {len(tweets)} tweets...')
|
|
||||||
# deleted_count = 0
|
|
||||||
# try:
|
|
||||||
# for tweet in tweets:
|
|
||||||
# print(f'deleted {deleted_count}/{len(tweets)}')
|
|
||||||
# await delete_tweet(tweet.id)
|
|
||||||
# await asyncio.sleep(0.5)
|
|
||||||
# deleted_count += 1
|
|
||||||
# except:
|
|
||||||
# print('Unhandled error occurred while trying to delete tweets.')
|
|
||||||
# traceback.print_exc()
|
|
||||||
# print('Try running again.')
|
|
||||||
# else:
|
|
||||||
# print('Saul Gone')
|
|
||||||
|
|
||||||
async def post_tweet(self, text='', media_ids: list=None, reply_to_tweet: int=None, quote_tweet_id: int=None):
|
async def post_tweet(self, text='', media_ids: list=None, reply_to_tweet: int=None, quote_tweet_id: int=None):
|
||||||
try:
|
try:
|
||||||
@@ -142,7 +106,7 @@ class TwAPI:
|
|||||||
|
|
||||||
# return True = successfully posted a single ttweet
|
# return True = successfully posted a single ttweet
|
||||||
# return False = did not post ttweet (duplicate)
|
# return False = did not post ttweet (duplicate)
|
||||||
async def post_ttweet(self, ttweet: tt.TalentTweet, is_catchup=False, dry_run=False):
|
async def post_ttweet(self, ttweet: tt.TalentTweet, dry_run=False):
|
||||||
print(f'------{ttweet.tweet_id} ({util.get_username_local(ttweet.author_id)})------')
|
print(f'------{ttweet.tweet_id} ({util.get_username_local(ttweet.author_id)})------')
|
||||||
|
|
||||||
text = ttweet.announce_text()
|
text = ttweet.announce_text()
|
||||||
@@ -167,9 +131,6 @@ class TwAPI:
|
|||||||
twt_resp = await self.post_tweet(text, quote_tweet_id=ttweet.tweet_id)
|
twt_resp = await self.post_tweet(text, quote_tweet_id=ttweet.tweet_id)
|
||||||
print('done')
|
print('done')
|
||||||
twt_id = twt_resp.data['id']
|
twt_id = twt_resp.data['id']
|
||||||
# if ttweet.reply_to is not None:
|
|
||||||
# re_ttweet = tt.TalentTweet(tweet_id=ttweet.reply_to, author_id=)
|
|
||||||
# media_ids.insert(0, await self.get_ttweet_image_media_id())
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print('creating reply img...', end='')
|
print('creating reply img...', end='')
|
||||||
|
|||||||
+6
-23
@@ -4,11 +4,12 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
import tweepy
|
import tweepy
|
||||||
import pytz
|
import pytz
|
||||||
import twint
|
import twint
|
||||||
#import twapi
|
import twapi
|
||||||
from tweetcapture import TweetCapture
|
from tweetcapture import TweetCapture
|
||||||
|
|
||||||
from recrop import fix_aspect_ratio
|
from recrop import fix_aspect_ratio
|
||||||
@@ -53,11 +54,12 @@ def get_key_from_value(d: dict, val):
|
|||||||
|
|
||||||
async def create_ttweet_image(ttweet):
|
async def create_ttweet_image(ttweet):
|
||||||
tc = TweetCapture()
|
tc = TweetCapture()
|
||||||
|
tc.cookies = [{'name': 'auth_token', 'value': dotenv_values()['web_auth_token']}]
|
||||||
if 'linux' in sys.platform:
|
if 'linux' in sys.platform:
|
||||||
# Linux chromedriver path
|
# Linux chromedriver path
|
||||||
tc.driver_path = '/usr/bin/chromedriver'
|
tc.driver_path = '/usr/bin/chromedriver'
|
||||||
filename = f'{get_project_dir()}/img.png'
|
filename = f'{get_project_dir()}/img.png'
|
||||||
url = ttweet_to_url(ttweet)
|
url = ttweet.url()
|
||||||
img = None
|
img = None
|
||||||
print(url)
|
print(url)
|
||||||
try: os.remove(filename)
|
try: os.remove(filename)
|
||||||
@@ -66,7 +68,7 @@ async def create_ttweet_image(ttweet):
|
|||||||
img = await tc.screenshot(
|
img = await tc.screenshot(
|
||||||
url=url,
|
url=url,
|
||||||
path=filename,
|
path=filename,
|
||||||
mode=4,
|
mode=0,
|
||||||
night_mode=1,
|
night_mode=1,
|
||||||
show_parent_tweets=True
|
show_parent_tweets=True
|
||||||
)
|
)
|
||||||
@@ -80,26 +82,7 @@ async def create_ttweet_image(ttweet):
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
def get_tweet_url(id, username):
|
def get_tweet_url(id, username):
|
||||||
return f'https://twitter.com/{username}/status/{id}'
|
return f'https://www.twitter.com/{username}/status/{id}'
|
||||||
|
|
||||||
def ttweet_to_url(ttweet):
|
|
||||||
username = get_username(ttweet.author_id)
|
|
||||||
return get_tweet_url(ttweet.tweet_id, username)
|
|
||||||
|
|
||||||
# twint
|
|
||||||
# May not work with short user IDs (ie. 1354241437)
|
|
||||||
# def get_username_online(id, default=None):
|
|
||||||
# c = twint.Config()
|
|
||||||
# c.User_id = id
|
|
||||||
# c.Store_object = True
|
|
||||||
# c.Hide_output = True
|
|
||||||
# try:
|
|
||||||
# twint.output.users_list.clear()
|
|
||||||
# twint.run.Lookup(c)
|
|
||||||
# user = twint.output.users_list[0]
|
|
||||||
# return user.username
|
|
||||||
# except:
|
|
||||||
# return str(default) if default is not None else f'{id}'
|
|
||||||
|
|
||||||
## Attempt to pull username from local; pull from online if doesn't exist.
|
## Attempt to pull username from local; pull from online if doesn't exist.
|
||||||
def get_username(id):
|
def get_username(id):
|
||||||
|
|||||||
Reference in New Issue
Block a user