Reworked old events, added deprecation warnings, and improved _send
- Added a new system to show an error if old events are used - Removed `test_data.json`, since I don't want to risk that anyone accidentally commits their username & password - Finished work on `sendEmoji` - Split `_send` into two parts: `_getSendData` and `_doSendRequest`, which allows for an easier way of adding new send requests
This commit is contained in:
235
fbchat/client.py
235
fbchat/client.py
@@ -24,7 +24,7 @@ from .event_hook import *
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Python 3 does not have raw_input, whereas Python 2 has and it's more secure
|
# Python 2's `input` executes the input, whereas `raw_input` just returns the input
|
||||||
try:
|
try:
|
||||||
input = raw_input
|
input = raw_input
|
||||||
except NameError:
|
except NameError:
|
||||||
@@ -95,6 +95,7 @@ class Client(object):
|
|||||||
self.threads = []
|
self.threads = []
|
||||||
|
|
||||||
self._setupEventHooks()
|
self._setupEventHooks()
|
||||||
|
self._setupOldEventHooks()
|
||||||
|
|
||||||
if not user_agent:
|
if not user_agent:
|
||||||
user_agent = choice(USER_AGENTS)
|
user_agent = choice(USER_AGENTS)
|
||||||
@@ -141,6 +142,7 @@ class Client(object):
|
|||||||
self.onFriendRequest = EventHook(from_id=str)
|
self.onFriendRequest = EventHook(from_id=str)
|
||||||
|
|
||||||
self.onUnknownMesssageType = EventHook(msg=dict)
|
self.onUnknownMesssageType = EventHook(msg=dict)
|
||||||
|
self.onMessageError = EventHook(exception=Exception, msg=dict)
|
||||||
|
|
||||||
# Setup event handlers
|
# Setup event handlers
|
||||||
self.onLoggingIn += lambda email: log.info("Logging in %s..." % email)
|
self.onLoggingIn += lambda email: log.info("Logging in %s..." % email)
|
||||||
@@ -164,8 +166,51 @@ class Client(object):
|
|||||||
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
|
self.onPersonRemoved += lambda removed_id, author_id, thread_id:\
|
||||||
log.info("%s removed: %s" % (author_id, removed_id))
|
log.info("%s removed: %s" % (author_id, removed_id))
|
||||||
|
|
||||||
self.onUnknownMesssageType += lambda msg:\
|
self.onUnknownMesssageType += lambda msg: log.info("Unknown message type received: %s" % msg)
|
||||||
log.info("Unknown message type received: %s" % msg)
|
self.onMessageError += lambda exception, msg: log.exception(exception)
|
||||||
|
|
||||||
|
def _checkOldEventHook(self, old_event, deprecated_in='0.10.2'):
|
||||||
|
if hasattr(type(self), old_event):
|
||||||
|
deprecation('Client.{}'.format(old_event), deprecated_in=deprecated_in, details='Use new event system instead')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _setupOldEventHooks(self):
|
||||||
|
if self._checkOldEventHook('on_message', deprecated_in='0.7.0'):
|
||||||
|
self.onMessage += lambda mid, author_id, message, thread_id, thread_type, ts, metadata:\
|
||||||
|
self.on_message(mid, author_id, None, message, metadata)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_message_new'):
|
||||||
|
self.onMessage += lambda mid, author_id, message, thread_id, thread_type, ts, metadata:\
|
||||||
|
self.on_message_new(mid, author_id, message, metadata, thread_id, True if thread_type is ThreadType.USER else False)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_friend_request'):
|
||||||
|
self.onFriendRequest += lambda from_id: self.on_friend_request(from_id)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_typing'):
|
||||||
|
self.onTyping += lambda author_id, typing_status: self.on_typing(author_id)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_read'):
|
||||||
|
self.onSeen += lambda seen_by, thread_id, timestamp: self.on_read(seen_by, thread_id, timestamp)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_people_added'):
|
||||||
|
self.onPeopleAdded += lambda added_ids, author_id, thread_id: self.on_people_added(added_ids, author_id, thread_id)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_person_removed'):
|
||||||
|
self.onPersonRemoved += lambda removed_id, author_id, thread_id: self.on_person_removed(removed_id, author_id, thread_id)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_inbox'):
|
||||||
|
self.onInbox += lambda unseen, unread, recent_unread: self.on_inbox(None, unseen, unread, None, recent_unread, None)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_qprimer'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_message_error'):
|
||||||
|
self.onMessageError += lambda exception, msg: self.on_message_error(exception, msg)
|
||||||
|
|
||||||
|
if self._checkOldEventHook('on_unknown_type'):
|
||||||
|
self.onUnknownMesssageType += lambda msg: self.on_unknown_type(msg)
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.6.0', details='Use log.<level> instead')
|
@deprecated(deprecated_in='0.6.0', details='Use log.<level> instead')
|
||||||
def _console(self, msg):
|
def _console(self, msg):
|
||||||
@@ -464,18 +509,8 @@ class Client(object):
|
|||||||
SEND METHODS
|
SEND METHODS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _send(self, thread_id=None, message=None, thread_type=ThreadType.USER, emoji_size=None, image_id=None, add_user_ids=None, new_title=None):
|
def _getSendData(self, thread_id=None, thread_type=ThreadType.USER):
|
||||||
# type: (str, str, ThreadType, EmojiSize, str, list, str) -> list
|
"""Returns the data needed to send a request to `SendURL`"""
|
||||||
"""Send a message with given thread id
|
|
||||||
|
|
||||||
:param thread_id: the user id or thread id that you want to send a message to
|
|
||||||
:param message: a text that you want to send
|
|
||||||
:param thread_type: determines if the recipient_id is for user or thread
|
|
||||||
:param emoji_size: size of the like sticker you want to send
|
|
||||||
:param image_id: id for the image to send, gotten from the UploadURL
|
|
||||||
:param add_user_ids: a list of user ids to add to a chat
|
|
||||||
:return: a list of message ids of the sent message(s)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if thread_id is None:
|
if thread_id is None:
|
||||||
if self.default_thread_id is not None:
|
if self.default_thread_id is not None:
|
||||||
@@ -522,36 +557,10 @@ class Client(object):
|
|||||||
elif thread_type == ThreadType.GROUP:
|
elif thread_type == ThreadType.GROUP:
|
||||||
data["thread_fbid"] = thread_id
|
data["thread_fbid"] = thread_id
|
||||||
|
|
||||||
# Set title
|
return data
|
||||||
if new_title:
|
|
||||||
data['action_type'] = 'ma-type:log-message'
|
|
||||||
data['log_message_data[name]'] = new_title
|
|
||||||
data['log_message_type'] = 'log:thread-name'
|
|
||||||
|
|
||||||
# Set users to add
|
|
||||||
if add_user_ids:
|
|
||||||
data['action_type'] = 'ma-type:log-message'
|
|
||||||
# It's possible to add multiple users
|
|
||||||
for i, add_user_id in enumerate(add_user_ids):
|
|
||||||
data['log_message_data[added_participants][' + str(i) + ']'] = "fbid:" + str(add_user_id)
|
|
||||||
data['log_message_type'] = 'log:subscribe'
|
|
||||||
|
|
||||||
# Sending a simple message
|
|
||||||
if not add_user_ids and not new_title:
|
|
||||||
data['action_type'] = 'ma-type:user-generated-message'
|
|
||||||
data['body'] = message or ''
|
|
||||||
data['has_attachment'] = image_id is not None
|
|
||||||
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
|
|
||||||
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
|
|
||||||
|
|
||||||
# Set image to send
|
|
||||||
if image_id:
|
|
||||||
data['image_ids[0]'] = image_id
|
|
||||||
|
|
||||||
# Set emoji to send
|
|
||||||
if emoji_size:
|
|
||||||
data["sticker_id"] = emoji_size.value
|
|
||||||
|
|
||||||
|
def _doSendRequest(self, data):
|
||||||
|
"""Sends the data to `SendURL`, and returns """
|
||||||
r = self._post(SendURL, data)
|
r = self._post(SendURL, data)
|
||||||
|
|
||||||
if not r.ok:
|
if not r.ok:
|
||||||
@@ -582,7 +591,14 @@ class Client(object):
|
|||||||
|
|
||||||
@deprecated(deprecated_in='0.10.2', details='Use specific functions (eg. sendMessage()) instead')
|
@deprecated(deprecated_in='0.10.2', details='Use specific functions (eg. sendMessage()) instead')
|
||||||
def send(self, recipient_id=None, message=None, is_user=True, like=None, image_id=None, add_user_ids=None):
|
def send(self, recipient_id=None, message=None, is_user=True, like=None, image_id=None, add_user_ids=None):
|
||||||
return self._send(thread_id=recipient_id, message=message, thread_type=isUserToThreadType(is_user), emoji_size=LIKES[like], image_id=image_id, add_user_ids=add_user_ids)
|
if add_user_ids:
|
||||||
|
return self.addUsersToChat(user_ids=add_user_ids, thread_id=recipient_id)
|
||||||
|
elif image_id:
|
||||||
|
return self.sendImage(image_id=image_id, message=message, thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
|
||||||
|
elif like:
|
||||||
|
return self.sendEmoji(emoji=None, size=LIKES[like], thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
|
||||||
|
else:
|
||||||
|
return self.sendMessage(message, thread_id=recipient_id, thread_type=isUserToThreadType(is_user))
|
||||||
|
|
||||||
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
def sendMessage(self, message, thread_id=None, thread_type=ThreadType.USER):
|
||||||
# type: (str, str, ThreadType) -> list
|
# type: (str, str, ThreadType) -> list
|
||||||
@@ -594,20 +610,54 @@ class Client(object):
|
|||||||
:param thread_type: specify whether thread_id is user or group chat
|
:param thread_type: specify whether thread_id is user or group chat
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type)
|
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
||||||
|
|
||||||
def sendEmoji(self, emoji=None, size=EmojiSize.SMALL, thread_id=None, thread_type=ThreadType.USER):
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
|
data['body'] = message or ''
|
||||||
|
data['has_attachment'] = False
|
||||||
|
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
|
||||||
|
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
|
def sendEmoji(self, emoji=None, size=EmojiSize.LARGE, thread_id=None, thread_type=ThreadType.USER):
|
||||||
# type: (str, EmojiSize, str, ThreadType) -> list
|
# type: (str, EmojiSize, str, ThreadType) -> list
|
||||||
"""
|
"""
|
||||||
Sends an emoji to given (or default, if not) thread.
|
Sends an emoji to given (or default, if not) thread.
|
||||||
|
|
||||||
:param emoji: WIP
|
:param emoji: the chosen emoji to send
|
||||||
:param size: size of emoji to send
|
:param size: size of emoji to send
|
||||||
:param thread_id: user/group chat ID
|
:param thread_id: user/group chat ID
|
||||||
:param thread_type: specify whether thread_id is user or group chat
|
:param thread_type: specify whether thread_id is user or group chat
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, thread_type=thread_type, emoji_size=size)
|
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
||||||
|
|
||||||
|
if emoji:
|
||||||
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
|
data['body'] = emoji or ''
|
||||||
|
data['has_attachment'] = False
|
||||||
|
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
|
||||||
|
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
|
||||||
|
data['tags[0]'] = 'hot_emoji_size:' + size['name']
|
||||||
|
else:
|
||||||
|
data["sticker_id"] = size['value']
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
|
def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER):
|
||||||
|
"""Sends an already uploaded image with the id image_id to the thread"""
|
||||||
|
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
||||||
|
|
||||||
|
data['action_type'] = 'ma-type:user-generated-message'
|
||||||
|
data['body'] = message or ''
|
||||||
|
data['has_attachment'] = True
|
||||||
|
data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
|
||||||
|
data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
|
||||||
|
|
||||||
|
data['image_ids[0]'] = image_id
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER,
|
def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER,
|
||||||
recipient_id=None, is_user=None, image=None):
|
recipient_id=None, is_user=None, image=None):
|
||||||
@@ -632,7 +682,7 @@ class Client(object):
|
|||||||
mimetype = guess_type(image_url)[0]
|
mimetype = guess_type(image_url)[0]
|
||||||
remote_image = requests.get(image_url).content
|
remote_image = requests.get(image_url).content
|
||||||
image_id = self._uploadImage({'file': (image_url, remote_image, mimetype)})
|
image_id = self._uploadImage({'file': (image_url, remote_image, mimetype)})
|
||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
|
||||||
|
|
||||||
# Doesn't upload properly
|
# Doesn't upload properly
|
||||||
def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER,
|
def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER,
|
||||||
@@ -658,7 +708,7 @@ class Client(object):
|
|||||||
image_path = image
|
image_path = image
|
||||||
mimetype = guess_type(image_path)[0]
|
mimetype = guess_type(image_path)[0]
|
||||||
image_id = self._uploadImage({'file': (image_path, open(image_path, 'rb'), mimetype)})
|
image_id = self._uploadImage({'file': (image_path, open(image_path, 'rb'), mimetype)})
|
||||||
return self._send(thread_id=thread_id, message=message, thread_type=thread_type, image_id=image_id)
|
return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type)
|
||||||
|
|
||||||
def addUsersToChat(self, user_ids, thread_id=None):
|
def addUsersToChat(self, user_ids, thread_id=None):
|
||||||
# type: (list, str) -> list
|
# type: (list, str) -> list
|
||||||
@@ -669,7 +719,16 @@ class Client(object):
|
|||||||
:param thread_id: group chat ID
|
:param thread_id: group chat ID
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, add_user_ids=user_ids)
|
|
||||||
|
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
||||||
|
|
||||||
|
data['action_type'] = 'ma-type:log-message'
|
||||||
|
data['log_message_type'] = 'log:subscribe'
|
||||||
|
|
||||||
|
for i, user_id in enumerate(user_ids):
|
||||||
|
data['log_message_data[added_participants][' + str(i) + ']'] = "fbid:" + str(user_id)
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
def removeUserFromChat(self, user_id, thread_id=None):
|
def removeUserFromChat(self, user_id, thread_id=None):
|
||||||
# type: (str, str) -> bool
|
# type: (str, str) -> bool
|
||||||
@@ -715,7 +774,14 @@ class Client(object):
|
|||||||
:param thread_id: group chat ID
|
:param thread_id: group chat ID
|
||||||
:return: a list of message ids of the sent message(s)
|
:return: a list of message ids of the sent message(s)
|
||||||
"""
|
"""
|
||||||
return self._send(thread_id=thread_id, thread_type=ThreadType.GROUP, new_title=title)
|
|
||||||
|
data = self._getSendData(thread_id=thread_id, thread_type=ThreadType.GROUP)
|
||||||
|
|
||||||
|
data['action_type'] = 'ma-type:log-message'
|
||||||
|
data['log_message_data[name]'] = title
|
||||||
|
data['log_message_type'] = 'log:thread-name'
|
||||||
|
|
||||||
|
return self._doSendRequest(data)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
END SEND METHODS
|
END SEND METHODS
|
||||||
@@ -1061,7 +1127,7 @@ class Client(object):
|
|||||||
self.onUnknownMesssageType(msg=m)
|
self.onUnknownMesssageType(msg=m)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(str(e))
|
self.onMessageError(exception=e, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
|
@deprecated(deprecated_in='0.10.2', details='Use startListening() instead')
|
||||||
@@ -1140,64 +1206,3 @@ class Client(object):
|
|||||||
if len(full_data)==1:
|
if len(full_data)==1:
|
||||||
full_data=full_data[0]
|
full_data=full_data[0]
|
||||||
return full_data
|
return full_data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_message_new(self, mid, author_id, message, metadata, recipient_id, thread_type):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event
|
|
||||||
|
|
||||||
This version of on_message recieves recipient_id and thread_type.
|
|
||||||
For backwards compatability, this data is sent directly to the old on_message.
|
|
||||||
"""
|
|
||||||
self.on_message(mid, author_id, None, message, metadata)
|
|
||||||
|
|
||||||
def on_message(self, mid, author_id, author_name, message, metadata):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
self.markAsDelivered(author_id, mid)
|
|
||||||
self.markAsRead(author_id)
|
|
||||||
log.info("%s said: %s" % (author_name, message))
|
|
||||||
|
|
||||||
|
|
||||||
def on_friend_request(self, from_id):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
log.info("Friend request from %s." % from_id)
|
|
||||||
|
|
||||||
|
|
||||||
def on_typing(self, author_id):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def on_read(self, author, reader, time):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def on_people_added(self, user_ids, actor_id, thread_id):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
log.info("User(s) {} was added to {} by {}".format(repr(user_ids), thread_id, actor_id))
|
|
||||||
|
|
||||||
|
|
||||||
def on_person_removed(self, user_id, actor_id, thread_id):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
log.info("User {} was removed from {} by {}".format(user_id, thread_id, actor_id))
|
|
||||||
|
|
||||||
|
|
||||||
def on_inbox(self, viewer, unseen, unread, other_unseen, other_unread, timestamp):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def on_message_error(self, exception, message):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
log.warning("Exception:\n{}".format(exception))
|
|
||||||
|
|
||||||
|
|
||||||
def on_qprimer(self, timestamp):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def on_unknown_type(self, m):
|
|
||||||
"""subclass Client and override this method to add custom behavior on event"""
|
|
||||||
log.debug("Unknown type {}".format(m))
|
|
||||||
|
|
||||||
|
@@ -72,12 +72,19 @@ class TypingStatus(Enum):
|
|||||||
DELETED = 0
|
DELETED = 0
|
||||||
TYPING = 1
|
TYPING = 1
|
||||||
|
|
||||||
|
|
||||||
# WIP
|
|
||||||
class EmojiSize(Enum):
|
class EmojiSize(Enum):
|
||||||
LARGE = '369239383222810'
|
LARGE = {
|
||||||
MEDIUM = '369239343222814'
|
'value': '369239383222810',
|
||||||
SMALL = '369239263222822'
|
'name': 'large'
|
||||||
|
}
|
||||||
|
MEDIUM = {
|
||||||
|
'value': '369239343222814',
|
||||||
|
'name': 'medium'
|
||||||
|
}
|
||||||
|
SMALL = {
|
||||||
|
'value': '369239263222822',
|
||||||
|
'name': 'small'
|
||||||
|
}
|
||||||
|
|
||||||
LIKES = {
|
LIKES = {
|
||||||
'l': EmojiSize.LARGE,
|
'l': EmojiSize.LARGE,
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"email": "",
|
|
||||||
"password": "",
|
|
||||||
"user_thread_id": "",
|
|
||||||
"group_thread_id": ""
|
|
||||||
}
|
|
9
tests.py
9
tests.py
@@ -18,7 +18,14 @@ logging.basicConfig(level=logging.INFO)
|
|||||||
Tests for fbchat
|
Tests for fbchat
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
To use these tests, fill in test_data.json or type this information manually in the terminal prompts.
|
To use these tests, make a json file called test_data.json, put this example in it, and fill in the gaps:
|
||||||
|
{
|
||||||
|
"email": "example@email.com",
|
||||||
|
"password": "example_password",
|
||||||
|
"group_thread_id": 0,
|
||||||
|
"user_thread_id": 0
|
||||||
|
}
|
||||||
|
or type this information manually in the terminal prompts.
|
||||||
|
|
||||||
- email: Your (or a test user's) email / phone number
|
- email: Your (or a test user's) email / phone number
|
||||||
- password: Your (or a test user's) password
|
- password: Your (or a test user's) password
|
||||||
|
Reference in New Issue
Block a user