mirror of
https://github.com/trevorhobenshield/twitter-api-client.git
synced 2025-12-19 09:58:30 -05:00
add more dm operations
This commit is contained in:
22
readme.md
22
readme.md
@@ -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({
|
||||
|
||||
23
setup.py
23
setup.py
@@ -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({
|
||||
|
||||
4
setup.sh
4
setup.sh
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
python3 -m build
|
||||
python3 -m twine upload dist/*
|
||||
python -m build
|
||||
python -m twine upload dist/*
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user