add more dm operations

This commit is contained in:
trevor hobenshield
2023-06-18 11:59:00 -07:00
parent 42b7dfb66a
commit 89a29e68da
5 changed files with 106 additions and 29 deletions

View File

@@ -119,14 +119,24 @@ latest_timeline = account.home_latest_timeline(limit=500)
# get bookmarks
bookmarks = account.bookmarks()
# get all dms
dms = account.dm_history(['12345-67890'])
# get DM inbox metadata
inbox = account.dm_inbox()
# search dms
dms = account.dm_search('test')
# get DMs from all conversations
dms = account.dm_history()
# get DMs from specific conversations
dms = account.dm_history(['123456-789012', '345678-901234'])
# search DMs by keyword
dms = account.dm_search('test123')
# delete entire conversation
account.dm_delete(conversation_id='123456-789012')
# delete (hide) specific DM
account.dm_delete(message_id='123456')
# delete conversation
account.dm_delete('12345-67890')
# example configuration
account.update_settings({

View File

@@ -14,7 +14,7 @@ install_requires = [
setup(
name="twitter-api-client",
version="0.9.6",
version="0.9.7",
python_requires=">=3.10.10",
description="Twitter API",
long_description=dedent('''
@@ -138,14 +138,23 @@ setup(
# get bookmarks
bookmarks = account.bookmarks()
# get all dms
dms = account.dm_history(['12345-67890'])
# get DM inbox metadata
inbox = account.dm_inbox()
# search dms
dms = account.dm_search('test')
# get DMs from all conversations
dms = account.dm_history()
# delete conversation
account.dm_delete('12345-67890')
# get DMs from specific conversations
dms = account.dm_history(['123456-789012', '345678-901234'])
# search DMs by keyword
dms = account.dm_search('test123')
# delete entire conversation
account.dm_delete(conversation_id='123456-789012')
# delete (hide) specific DM
account.dm_delete(message_id='123456')
# example configuration
account.update_settings({

View File

@@ -1,4 +1,4 @@
#!/usr/bin/bash
python3 -m build
python3 -m twine upload dist/*
python -m build
python -m twine upload dist/*

View File

@@ -606,15 +606,37 @@ class Account:
raise Exception('Session not authenticated. '
'Please use an authenticated session or remove the `session` argument and try again.')
def dm_history(self, conversation_ids: list[str]) -> list[dict]:
def dm_inbox(self) -> dict:
"""
Get DM inbox metadata.
@return: inbox as dict
"""
r = self.session.get(
f'{self.v1_api}/dm/inbox_initial_state.json',
headers=get_headers(self.session),
params=dm_params
)
return r.json()
def dm_history(self, conversation_ids: list[str] = None) -> list[dict]:
"""
Get DM history.
Call without arguments to get all DMS from all conversations.
@param conversation_ids: optional list of conversation ids
@return: list of messages as dicts
"""
async def get(session: AsyncClient, conversation_id: str):
params = deepcopy(dm_history_params)
params = deepcopy(dm_params)
r = await session.get(
f'{self.v1_api}/dm/conversation/{conversation_id}.json',
params=params,
)
res = r.json().get('conversation_timeline', {})
data = [x['message'] for x in res.get('entries', [])]
data = [x.get('message') for x in res.get('entries', [])]
entry_id = res.get('min_entry_id')
while entry_id:
params['max_id'] = entry_id
@@ -627,33 +649,68 @@ class Account:
entry_id = res.get('min_entry_id')
return data
async def process():
async def process(ids):
limits = Limits(max_connections=100)
headers, cookies = get_headers(self.session), self.session.cookies
async with AsyncClient(limits=limits, headers=headers, cookies=cookies, timeout=20) as c:
return await tqdm_asyncio.gather(*(get(c, _id) for _id in conversation_ids), desc="Getting DMs")
return await tqdm_asyncio.gather(*(get(c, _id) for _id in ids), desc="Getting DMs")
return asyncio.run(process())
if conversation_ids:
ids = conversation_ids
else:
# get all conversations
inbox = self.dm_inbox()
ids = list(inbox['inbox_initial_state']['conversations'])
def dm_delete(self, conversation_id: str):
return self.session.post(
f'{self.v1_api}/dm/conversation/{conversation_id}/delete.json',
headers=get_headers(self.session),
)
return asyncio.run(process(ids))
def dm_delete(self, *, conversation_id: str = None, message_id: str = None) -> dict:
"""
Delete operations
- delete (hide) a single DM
- delete an entire conversation
@param conversation_id: the conversation id
@param message_id: the message id
@return: result metadata
"""
self.session.headers.update(headers=get_headers(self.session))
results = {'conversation': None, 'message': None}
if conversation_id:
results['conversation'] = self.session.post(
f'{self.v1_api}/dm/conversation/{conversation_id}/delete.json',
).text # not json response
if message_id:
# delete single message
_id, op = Operation.DMMessageDeleteMutation
results['message'] = self.session.post(
f'https://twitter.com/i/api/graphql/{_id}/{op}',
json={'queryId': _id, 'variables': {'messageId': message_id}},
).json()
return results
def dm_search(self, query: str) -> dict:
"""
Search DMs by keyword
@param query: search term
@return: search results as dict
"""
def dm_search(self, query: str):
def get(cursor=None):
if cursor:
params['variables']['cursor'] = cursor.pop()
_id, op = Operation.DmAllSearchSlice
r = self.session.get(
f'https://twitter.com/i/api/graphql/{_id}/{op}',
params=build_params(params)
params=build_params(params),
)
res = r.json()
cursor = find_key(res, 'next_cursor')
return res, cursor
self.session.headers.update(headers=get_headers(self.session))
variables = deepcopy(Operation.default_variables)
variables['count'] = 50 # strict limit, errors thrown if exceeded
variables['query'] = query

View File

@@ -215,6 +215,7 @@ class Operation:
DmAllSearchSlice = 'U-QXVRZ6iddb1QuZweh5DQ', 'DmAllSearchSlice'
DmGroupSearchSlice = '5zpY1dCR-8NyxQJS_CFJoQ', 'DmGroupSearchSlice'
DmMutedTimeline = 'lrcWa13oyrQc7L33wRdLAQ', 'DmMutedTimeline'
DMMessageDeleteMutation = 'BJ6DtxA2llfjnRoRjaiIiw', 'DMMessageDeleteMutation'
DmNsfwMediaFilterUpdate = 'of_N6O33zfyD4qsFJMYFxA', 'DmNsfwMediaFilterUpdate'
DmPeopleSearchSlice = 'xYSm8m5kJnzm_gFCn5GH-w', 'DmPeopleSearchSlice'
EditBookmarkFolder = 'a6kPp1cS1Dgbsjhapz1PNw', 'EditBookmarkFolder'
@@ -561,7 +562,7 @@ search_config = {
'ext': 'mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,birdwatchPivot,enrichments,superFollowMetadata,unmentionInfo,editControl,collab_control,vibe'
}
dm_history_params = {
dm_params = {
'context': 'FETCH_DM_CONVERSATION',
'include_profile_interstitial_type': '1',
'include_blocking': '1',