message-type: Add support for "direct" as value for type parameter.

For endpoints with a `type` parameter to indicate whether the message
is a stream or direct message, `POST /typing` and `POST /messages`,
adds support for passing "direct" as the preferred value for direct
messages, group and 1-on-1.

Maintains support for "private" as a deprecated value to indicate
direct messages.

Fixes #24960.
This commit is contained in:
Lauryn Menard 2023-04-17 17:02:07 +02:00 committed by Tim Abbott
parent 42d9560413
commit 2c043c6242
11 changed files with 173 additions and 97 deletions

View File

@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 7.0 ## Changes in Zulip 7.0
**Feature level 174**:
* [`POST /typing`](/api/set-typing-status), [`POST /messages`](/api/send-message):
Added `"direct"` as the preferred way to indicate a direct message for the
`type` parameter, deprecating the original `"private"`. While `"private"`
is still supported for direct messages, clients are encouraged to use to
the modern convention with servers that support it, because support for
`"private"` will eventually be removed.
**Feature level 173**: **Feature level 173**:
* [`GET /scheduled_messages`](/api/get-scheduled-messages), [`DELETE * [`GET /scheduled_messages`](/api/get-scheduled-messages), [`DELETE

View File

@ -19,10 +19,10 @@ curl -X POST {{ api_url }}/v1/messages \
--data-urlencode topic=Castle \ --data-urlencode topic=Castle \
--data-urlencode 'content=I come not, friends, to steal away your hearts.' --data-urlencode 'content=I come not, friends, to steal away your hearts.'
# For private messages # For direct messages
curl -X POST {{ api_url }}/v1/messages \ curl -X POST {{ api_url }}/v1/messages \
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \ -u BOT_EMAIL_ADDRESS:BOT_API_KEY \
--data-urlencode type=private \ --data-urlencode type=direct \
--data-urlencode 'to=[9]' \ --data-urlencode 'to=[9]' \
--data-urlencode 'content=With mirth and laughter let old wrinkles come.' --data-urlencode 'content=With mirth and laughter let old wrinkles come.'
``` ```
@ -38,7 +38,7 @@ the command-line, providing the message content via STDIN.
zulip-send --stream Denmark --subject Castle \ zulip-send --stream Denmark --subject Castle \
--user othello-bot@example.com --api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 --user othello-bot@example.com --api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5
# For private messages # For direct messages
zulip-send hamlet@example.com \ zulip-send hamlet@example.com \
--user othello-bot@example.com --api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 --user othello-bot@example.com --api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5
``` ```

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 173 API_FEATURE_LEVEL = 174
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -3023,6 +3023,16 @@ class ArchivedMessage(AbstractMessage):
class Message(AbstractMessage): class Message(AbstractMessage):
# Recipient types used when a Message object is provided to
# Zulip clients via the API.
#
# A detail worth noting:
# * "direct" was introduced in 2023 with the goal of
# deprecating the original "private" and becoming the
# preferred way to indicate a personal or huddle
# Recipient type via the API.
API_RECIPIENT_TYPES = ["direct", "private", "stream"]
search_tsvector = SearchVectorField(null=True) search_tsvector = SearchVectorField(null=True)
def topic_name(self) -> str: def topic_name(self) -> str:

View File

@ -113,11 +113,11 @@ add_example("send_message", "/messages:post", 200, async (client, console) => {
}; };
console.log(await client.messages.send(params)); console.log(await client.messages.send(params));
// Send a private message // Send a direct message
const user_id = 9; const user_id = 9;
params = { params = {
to: [user_id], to: [user_id],
type: "private", type: "direct",
content: "With mirth and laughter let old wrinkles come.", content: "With mirth and laughter let old wrinkles come.",
}; };
console.log(await client.messages.send(params)); console.log(await client.messages.send(params));
@ -252,7 +252,8 @@ add_example("set_typing_status", "/typing:post", 200, async (client, console) =>
to: [user_id1, user_id2], to: [user_id1, user_id2],
}; };
// The user has started to type in the group PM with Iago and Polonius // The user has started typing in the group direct message
// with Iago and Polonius
console.log(await client.typing.send(typingParams)); console.log(await client.typing.send(typingParams));
// {code_example|end} // {code_example|end}
}); });

View File

@ -954,6 +954,7 @@ def send_message(client: Client) -> int:
"content": "I come not, friends, to steal away your hearts.", "content": "I come not, friends, to steal away your hearts.",
} }
result = client.send_message(request) result = client.send_message(request)
# {code_example|end} # {code_example|end}
validate_against_openapi_schema(result, "/messages", "post", "200") validate_against_openapi_schema(result, "/messages", "post", "200")
@ -971,7 +972,7 @@ def send_message(client: Client) -> int:
ensure_users([10], ["hamlet"]) ensure_users([10], ["hamlet"])
# {code_example|start} # {code_example|start}
# Send a private message # Send a direct message
user_id = 10 user_id = 10
request = { request = {
"type": "private", "type": "private",
@ -979,6 +980,7 @@ def send_message(client: Client) -> int:
"content": "With mirth and laughter let old wrinkles come.", "content": "With mirth and laughter let old wrinkles come.",
} }
result = client.send_message(request) result = client.send_message(request)
# {code_example|end} # {code_example|end}
validate_against_openapi_schema(result, "/messages", "post", "200") validate_against_openapi_schema(result, "/messages", "post", "200")
@ -1294,7 +1296,8 @@ def set_typing_status(client: Client) -> None:
ensure_users([10, 11], ["hamlet", "iago"]) ensure_users([10, 11], ["hamlet", "iago"])
# {code_example|start} # {code_example|start}
# The user has started to type in the group PM with Iago and Polonius # The user has started typing in the group direct message
# with Iago and Polonius
user_id1 = 10 user_id1 = 10
user_id2 = 11 user_id2 = 11
@ -1309,7 +1312,8 @@ def set_typing_status(client: Client) -> None:
validate_against_openapi_schema(result, "/typing", "post", "200") validate_against_openapi_schema(result, "/typing", "post", "200")
# {code_example|start} # {code_example|start}
# The user has finished typing in the group PM with Iago and Polonius # The user has finished typing in the group direct message
# with Iago and Polonius
user_id1 = 10 user_id1 = 10
user_id2 = 11 user_id2 = 11
@ -1324,7 +1328,8 @@ def set_typing_status(client: Client) -> None:
validate_against_openapi_schema(result, "/typing", "post", "200") validate_against_openapi_schema(result, "/typing", "post", "200")
# {code_example|start} # {code_example|start}
# The user has started to type in topic "typing status" of stream "Denmark" # The user has started to type in topic "typing status"
# of stream "Denmark"
stream_id = client.get_stream_id("Denmark")["stream_id"] stream_id = client.get_stream_id("Denmark")["stream_id"]
topic = "typing status" topic = "typing status"
@ -1341,7 +1346,8 @@ def set_typing_status(client: Client) -> None:
validate_against_openapi_schema(result, "/typing", "post", "200") validate_against_openapi_schema(result, "/typing", "post", "200")
# {code_example|start} # {code_example|start}
# The user has finished typing in topic "typing status" of stream "Denmark" # The user has finished typing in topic "typing status"
# of stream "Denmark"
stream_id = client.get_stream_id("Denmark")["stream_id"] stream_id = client.get_stream_id("Denmark")["stream_id"]
topic = "typing status" topic = "typing status"

View File

@ -5502,25 +5502,35 @@ paths:
summary: Send a message summary: Send a message
tags: ["messages"] tags: ["messages"]
description: | description: |
Send a stream or a private message. Send a [stream message](/help/streams-and-topics) or a
[direct message](/help/direct-messages).
parameters: parameters:
- name: type - name: type
in: query in: query
description: | description: |
The type of message to be sent. `private` for a private message and The type of message to be sent.
`stream` for a stream message.
`"direct"` for a direct message and `"stream"` for a stream message.
**Changes**: In Zulip 7.0 (feature level 174), `"direct"` was added as
the preferred way to request a direct message, deprecating the original
`"private"`. While `"private"` is still supported for requesting direct
messages, clients are encouraged to use to the modern convention with
servers that support it, because support for `"private"` will eventually
be removed.
schema: schema:
type: string type: string
enum: enum:
- private - direct
- stream - stream
example: private - private
example: direct
required: true required: true
- name: to - name: to
in: query in: query
description: | description: |
For stream messages, either the name or integer ID of the stream. For For stream messages, either the name or integer ID of the stream. For
private messages, either a list containing integer user IDs or a list direct messages, either a list containing integer user IDs or a list
containing string email addresses. containing string email addresses.
**Changes**: Support for using user/stream IDs was added in Zulip 2.0.0. **Changes**: Support for using user/stream IDs was added in Zulip 2.0.0.
@ -5624,7 +5634,7 @@ paths:
"result": "error", "result": "error",
} }
description: | description: |
A typical failed JSON response for when a private message is sent to a user A typical failed JSON response for when a direct message is sent to a user
that does not exist: that does not exist:
/messages/{message_id}/history: /messages/{message_id}/history:
get: get:
@ -15056,7 +15066,7 @@ paths:
summary: Set "typing" status summary: Set "typing" status
tags: ["users"] tags: ["users"]
description: | description: |
Notify other users whether the current user is typing a message. Notify other users whether the current user is [typing a message](/help/typing-notifications).
Clients implementing Zulip's typing notifications protocol should work as follows: Clients implementing Zulip's typing notifications protocol should work as follows:
@ -15100,15 +15110,21 @@ paths:
description: | description: |
Type of the message being composed. Type of the message being composed.
**Changes**: New in Zulip 4.0 (feature level 58). Previously, typing **Changes**: In Zulip 7.0 (feature level 174), `"direct"` was added
notifications were only for private messages. as the preferred way to indicate a direct message is being composed,
becoming the default value for this parameter and deprecating the
original `"private"`.
New in Zulip 4.0 (feature level 58). Previously, typing notifications
were only for direct messages.
schema: schema:
type: string type: string
enum: enum:
- private - direct
- stream - stream
default: private - private
example: private default: direct
example: direct
- name: op - name: op
in: query in: query
description: | description: |
@ -15123,16 +15139,16 @@ paths:
- name: to - name: to
in: query in: query
description: | description: |
For `"private"` type it is the user_ids of the recipients of the message being typed. For `"direct"` type it is the user IDs of the recipients of the message
Send a JSON-encoded list of user_ids. (Use a list even if there is only one being typed. Send a JSON-encoded list of user IDs. (Use a list even if
recipient.) there is only one recipient.)
For `"stream"` type it is a single element list containing ID of stream in For `"stream"` type it is a single element list containing ID of stream in
which the message is being typed. which the message is being typed.
**Changes**: Support for typing notifications for stream messages **Changes**: Support for typing notifications for stream messages
is new in Zulip 4.0 (feature level 58). Previously, typing is new in Zulip 4.0 (feature level 58). Previously, typing
notifications were only for private messages. notifications were only for direct messages.
Before Zulip 2.0.0, this parameter accepted only a JSON-encoded Before Zulip 2.0.0, this parameter accepted only a JSON-encoded
list of email addresses. Support for the email address-based format was list of email addresses. Support for the email address-based format was
@ -15149,10 +15165,10 @@ paths:
in: query in: query
description: | description: |
Topic to which message is being typed. Required for the `"stream"` type. Topic to which message is being typed. Required for the `"stream"` type.
Ignored in the case of `"private"` type. Ignored in the case of `"direct"` type.
**Changes**: New in Zulip 4.0 (feature level 58). Previously, typing **Changes**: New in Zulip 4.0 (feature level 58). Previously, typing
notifications were only for private messages. notifications were only for direct messages.
schema: schema:
type: string type: string
example: typing notifications example: typing notifications

View File

@ -586,7 +586,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": orjson.dumps([othello.email]).decode(), "to": orjson.dumps([othello.email]).decode(),
}, },
@ -605,7 +605,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": orjson.dumps([user_profile.email]).decode(), "to": orjson.dumps([user_profile.email]).decode(),
}, },
@ -628,52 +628,60 @@ class MessagePOSTTest(ZulipTestCase):
def test_personal_message_by_id(self) -> None: def test_personal_message_by_id(self) -> None:
""" """
Sending a personal message to a valid user ID is successful. Sending a personal message to a valid user ID is successful
for both valid strings for `type` parameter.
""" """
self.login("hamlet") self.login("hamlet")
result = self.client_post( recipient_type_name = ["direct", "private"]
"/json/messages",
{
"type": "private",
"content": "Test message",
"to": orjson.dumps([self.example_user("othello").id]).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message() for type in recipient_type_name:
self.assertEqual("Test message", msg.content) result = self.client_post(
self.assertEqual(msg.recipient_id, self.example_user("othello").recipient_id) "/json/messages",
{
"type": type,
"content": "Test message",
"to": orjson.dumps([self.example_user("othello").id]).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual("Test message", msg.content)
self.assertEqual(msg.recipient_id, self.example_user("othello").recipient_id)
def test_group_personal_message_by_id(self) -> None: def test_group_personal_message_by_id(self) -> None:
""" """
Sending a personal message to a valid user ID is successful. Sending a personal message to a valid user ID is successful
for both valid strings for `type` parameter.
""" """
self.login("hamlet") self.login("hamlet")
result = self.client_post( recipient_type_name = ["direct", "private"]
"/json/messages",
{
"type": "private",
"content": "Test message",
"to": orjson.dumps(
[self.example_user("othello").id, self.example_user("cordelia").id]
).decode(),
},
)
self.assert_json_success(result)
msg = self.get_last_message() for type in recipient_type_name:
self.assertEqual("Test message", msg.content) result = self.client_post(
self.assertEqual( "/json/messages",
msg.recipient_id,
get_huddle_recipient(
{ {
self.example_user("hamlet").id, "type": type,
self.example_user("othello").id, "content": "Test message",
self.example_user("cordelia").id, "to": orjson.dumps(
} [self.example_user("othello").id, self.example_user("cordelia").id]
).id, ).decode(),
) },
)
self.assert_json_success(result)
msg = self.get_last_message()
self.assertEqual("Test message", msg.content)
self.assertEqual(
msg.recipient_id,
get_huddle_recipient(
{
self.example_user("hamlet").id,
self.example_user("othello").id,
self.example_user("cordelia").id,
}
).id,
)
def test_personal_message_copying_self(self) -> None: def test_personal_message_copying_self(self) -> None:
""" """
@ -686,7 +694,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": orjson.dumps([hamlet.id, othello.id]).decode(), "to": orjson.dumps([hamlet.id, othello.id]).decode(),
}, },
@ -704,7 +712,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": "nonexistent", "to": "nonexistent",
}, },
@ -723,7 +731,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": orjson.dumps([othello.id]).decode(), "to": orjson.dumps([othello.id]).decode(),
}, },
@ -733,7 +741,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"to": orjson.dumps([othello.id, cordelia.id]).decode(), "to": orjson.dumps([othello.id, cordelia.id]).decode(),
}, },
@ -754,7 +762,7 @@ class MessagePOSTTest(ZulipTestCase):
"to": othello.email, "to": othello.email,
}, },
) )
self.assert_json_error(result, "Invalid message type") self.assert_json_error(result, "Invalid type")
def test_empty_message(self) -> None: def test_empty_message(self) -> None:
""" """
@ -764,7 +772,7 @@ class MessagePOSTTest(ZulipTestCase):
othello = self.example_user("othello") othello = self.example_user("othello")
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{"type": "private", "content": " ", "to": othello.email}, {"type": "direct", "content": " ", "to": othello.email},
) )
self.assert_json_error(result, "Message must not be empty") self.assert_json_error(result, "Message must not be empty")
@ -824,9 +832,9 @@ class MessagePOSTTest(ZulipTestCase):
) )
self.assert_json_error(result, "Invalid character in topic, at position 5!") self.assert_json_error(result, "Invalid character in topic, at position 5!")
def test_invalid_message_type(self) -> None: def test_invalid_recipient_type(self) -> None:
""" """
Messages other than the type of "private" or "stream" are considered as invalid Messages other than the type of "direct", "private" or "stream" are invalid.
""" """
self.login("hamlet") self.login("hamlet")
result = self.client_post( result = self.client_post(
@ -838,7 +846,7 @@ class MessagePOSTTest(ZulipTestCase):
"topic": "Test topic", "topic": "Test topic",
}, },
) )
self.assert_json_error(result, "Invalid message type") self.assert_json_error(result, "Invalid type")
def test_private_message_without_recipients(self) -> None: def test_private_message_without_recipients(self) -> None:
""" """
@ -847,7 +855,7 @@ class MessagePOSTTest(ZulipTestCase):
self.login("hamlet") self.login("hamlet")
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{"type": "private", "content": "Test content", "to": ""}, {"type": "direct", "content": "Test content", "to": ""},
) )
self.assert_json_error(result, "Message must have recipients") self.assert_json_error(result, "Message must have recipients")
@ -859,7 +867,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -879,7 +887,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -898,7 +906,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -916,7 +924,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -931,7 +939,7 @@ class MessagePOSTTest(ZulipTestCase):
Sending two mirrored huddles in the row return the same ID Sending two mirrored huddles in the row return the same ID
""" """
msg = { msg = {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -1065,7 +1073,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
"to": self.mit_email("starnine"), "to": self.mit_email("starnine"),
@ -1079,7 +1087,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "not-private", "type": "stream",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -1098,7 +1106,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -1120,7 +1128,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -1141,7 +1149,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "private", "type": "direct",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",

View File

@ -132,6 +132,23 @@ class TypingValidateToArgumentsTest(ZulipTestCase):
class TypingHappyPathTestPMs(ZulipTestCase): class TypingHappyPathTestPMs(ZulipTestCase):
def test_valid_type_and_op_parameters(self) -> None:
recipient_type_name = ["direct", "private"]
operator_type = ["start", "stop"]
sender = self.example_user("hamlet")
recipient_user = self.example_user("othello")
for type in recipient_type_name:
for operator in operator_type:
params = dict(
to=orjson.dumps([recipient_user.id]).decode(),
op=operator,
type=type,
)
result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result)
def test_start_to_single_recipient(self) -> None: def test_start_to_single_recipient(self) -> None:
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
recipient_user = self.example_user("othello") recipient_user = self.example_user("othello")

View File

@ -24,7 +24,7 @@ from zerver.lib.request import REQ, RequestNotes, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.timestamp import convert_to_UTC from zerver.lib.timestamp import convert_to_UTC
from zerver.lib.topic import REQ_topic from zerver.lib.topic import REQ_topic
from zerver.lib.validator import check_int, to_float from zerver.lib.validator import check_int, check_string_in, to_float
from zerver.lib.zcommand import process_zcommands from zerver.lib.zcommand import process_zcommands
from zerver.lib.zephyr import compute_mit_user_fullname from zerver.lib.zephyr import compute_mit_user_fullname
from zerver.models import ( from zerver.models import (
@ -193,7 +193,7 @@ def handle_deferred_message(
def send_message_backend( def send_message_backend(
request: HttpRequest, request: HttpRequest,
user_profile: UserProfile, user_profile: UserProfile,
message_type_name: str = REQ("type"), req_type: str = REQ("type", str_validator=check_string_in(Message.API_RECIPIENT_TYPES)),
req_to: Optional[str] = REQ("to", default=None), req_to: Optional[str] = REQ("to", default=None),
req_sender: Optional[str] = REQ("sender", default=None, documentation_pending=True), req_sender: Optional[str] = REQ("sender", default=None, documentation_pending=True),
forged_str: Optional[str] = REQ("forged", default=None, documentation_pending=True), forged_str: Optional[str] = REQ("forged", default=None, documentation_pending=True),
@ -210,7 +210,12 @@ def send_message_backend(
tz_guess: Optional[str] = REQ("tz_guess", default=None, documentation_pending=True), tz_guess: Optional[str] = REQ("tz_guess", default=None, documentation_pending=True),
time: Optional[float] = REQ(default=None, converter=to_float, documentation_pending=True), time: Optional[float] = REQ(default=None, converter=to_float, documentation_pending=True),
) -> HttpResponse: ) -> HttpResponse:
recipient_type_name = message_type_name recipient_type_name = req_type
if recipient_type_name == "direct":
# For now, use "private" from Message.API_RECIPIENT_TYPES.
# TODO: Use "direct" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "private"
# If req_to is None, then we default to an # If req_to is None, then we default to an
# empty list of recipients. # empty list of recipients.

View File

@ -9,18 +9,17 @@ from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.streams import access_stream_by_id, access_stream_for_send_message from zerver.lib.streams import access_stream_by_id, access_stream_for_send_message
from zerver.lib.validator import check_int, check_list, check_string_in from zerver.lib.validator import check_int, check_list, check_string_in
from zerver.models import UserProfile from zerver.models import Message, UserProfile
VALID_OPERATOR_TYPES = ["start", "stop"] VALID_OPERATOR_TYPES = ["start", "stop"]
VALID_MESSAGE_TYPES = ["private", "stream"]
@has_request_variables @has_request_variables
def send_notification_backend( def send_notification_backend(
request: HttpRequest, request: HttpRequest,
user_profile: UserProfile, user_profile: UserProfile,
message_type: str = REQ( req_type: str = REQ(
"type", str_validator=check_string_in(VALID_MESSAGE_TYPES), default="private" "type", str_validator=check_string_in(Message.API_RECIPIENT_TYPES), default="direct"
), ),
operator: str = REQ("op", str_validator=check_string_in(VALID_OPERATOR_TYPES)), operator: str = REQ("op", str_validator=check_string_in(VALID_OPERATOR_TYPES)),
notification_to: List[int] = REQ("to", json_validator=check_list(check_int)), notification_to: List[int] = REQ("to", json_validator=check_list(check_int)),
@ -31,7 +30,12 @@ def send_notification_backend(
if to_length == 0: if to_length == 0:
raise JsonableError(_("Empty 'to' list")) raise JsonableError(_("Empty 'to' list"))
if message_type == "stream": recipient_type_name = req_type
if recipient_type_name == "private":
# TODO: Use "direct" in typing notification events.
recipient_type_name = "direct"
if recipient_type_name == "stream":
if to_length > 1: if to_length > 1:
raise JsonableError(_("Cannot send to multiple streams")) raise JsonableError(_("Cannot send to multiple streams"))