使用MS Exchange Graph API Java SDK发送带有投票按钮的交换电子邮件
如何使用Graph API发送带有投票按钮的电子邮件?我知道如何使用EWS执行此操作,但我找不到任何描述如何使用Graph API执行此操作的内容。
解决方案
我已经包含了使用MS Graph API Java SDK发送带有投票按钮的电子邮件的所有代码。投票按钮标签在SingleValueLegacyExtendedProperty中发送。
// send an email
Message message = new Message();
message.subject = "Meet for lunch?";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "The new cafeteria is open.";
message.body = body;
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = to;
Recipient toRecipient = new Recipient();
toRecipient.emailAddress = emailAddress;
message.toRecipients = List.of(toRecipient);
EmailAddress fromAddress = new EmailAddress();
fromAddress.address = sendingMailbox;
Recipient fromRecipient = new Recipient();
fromRecipient.emailAddress = fromAddress;
message.from = fromRecipient;
VotingButtonEncoder vbe = new VotingButtonEncoderImpl();
SingleValueLegacyExtendedProperty prop =
new SingleValueLegacyExtendedProperty();
prop.value = vbe.createVoteButtonsBase64String(
List.of("Yes, let's have lunch.", "No, thank you though."));
// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxprops/e11cc753-cecf-4fdc-bec7-23304d12388a
prop.id = "Binary {00062008-0000-0000-C000-000000000046} Id 0x00008520";
List<com.microsoft.graph.options.Option> requestOptions =
new ArrayList<>();
String requestUrl = "https://graph.microsoft.com/v1.0/users/"
+ sendingMailbox + "/microsoft.graph.sendMail";
SingleValueLegacyExtendedPropertyCollectionRequestBuilder builder =
new SingleValueLegacyExtendedPropertyCollectionRequestBuilder(
requestUrl, graphClient, requestOptions);
List<SingleValueLegacyExtendedProperty> pageContents =
new ArrayList<>();
pageContents.add(prop);
SingleValueLegacyExtendedPropertyCollectionPage singleValueExtPropPage =
new SingleValueLegacyExtendedPropertyCollectionPage(
pageContents, builder);
message.singleValueExtendedProperties = singleValueExtPropPage;
boolean saveToSentItems = true;
graphClient.users(sendingMailbox)
.sendMail(UserSendMailParameterSet.newBuilder()
.withMessage(message)
.withSaveToSentItems(saveToSentItems).build())
.buildRequest().post();
投票按钮编码器:
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class VotingButtonEncoderImpl implements VotingButtonEncoder {
public static final String voteRequestPropHex = "0x00008520";
@Override
public String voteRequestId() {
return voteRequestPropHex;
}
@Override
public String createVoteButtonsBase64String(
Collection<String> voteOptions) throws DecoderException {
String hex = createVoteButtonsHexString(voteOptions);
byte[] bytes = Hex.decodeHex(hex.toCharArray());
return Base64.getEncoder().encodeToString(bytes);
}
@Override
public String createVoteButtonsHexString(Collection<String> voteOptions) {
// let's build a PidLidVerbStream!
// https://msdn.microsoft.com/en-us/library/ee218541(v=exchg.80).aspx
//
// that is a bit...dense...so how about the partial solution from
// Glen'S Exchange Blog
// http://gsexdev.blogspot.com/2015/01/sending-message-with-voting-buttons.html
// don't worry, as of 2017/12/11 the content there is totally A-OK. Just
// Make sure that the vote button strings are in the VoteOptionsExtra
// section.
List<VoteButtonHexifier> options =
new ArrayList<VoteButtonHexifier>(voteOptions.size());
for (String optionString : voteOptions) {
options.add(new VoteButtonHexifier(optionString));
}
String header = "0201";
// docs say count of VoteOption stuctures plus VoteOptionExtras
// structures, but appears to actually be the count of
// VoteOption + 4. Witness, here are the start of the binary from
// emails with 10, 9, 8 ... 1 voting options as created in
// Outlook. Look at the 4th and 5th hex digits which have been
// separated from the rest with a space. (Come to think of it,
// those extra 4 bytes are probably a null terminator for the
// string.)
//
// Email Options: one;two;three;four;five;six;seven;eight;nine;ten
// 0x8520: 0201 0e 00000000000000055265706c79084...
// Email Options: one;two;three;four;five;six;seven;eight;nine
// 0x8520: 0201 0d 00000000000000055265706c79084...
// Email Options: one;two;three;four;five;six;seven;eight
// 0x8520: 0201 0c 00000000000000055265706c79084...
// Email Options: one;two;three;four;five;six;seven
// 0x8520: 0201 0b 00000000000000055265706c79084...
// Email Options: one;two;three;four;five;six
// 0x8520: 0201 0a 00000000000000055265706c79084...
// Email Options: one;two;three;four;five
// 0x8520: 0201 09 00000000000000055265706c79084...
// Email Options: gov one;two;three;four
// 0x8520: 0201 08 00000000000000055265706c79084...
// Email Options: onebutton;twobutton;threebutton
// 0x8520: 0201 07 00000000000000055265706c79084...
// Email Options: onebutton;twobutton
// 0x8520: 0201 06 00000000000000055265706c79084...
// Email Options: one button
// 0x8520: 0201 05 00000000000000055265706c79084...
String recordCnt = intToLittleEndianString(options.size() + 4);
// not documented anywhere, but seems necessary (null terminator?)
String preReplyAllPadding = "00000000";
String replyToAllHeader =
"055265706C790849504D2E4E6F7465074D657373616765025245050000000000000000";
String replyToAllFooter =
"0000000000000002000000660000000200000001000000";
String replyToHeader =
"0C5265706C7920746F20416C6C0849504D2E4E6F7465074D657373616765025245050000000000000000";
String replyToFooter = "0000000000000002000000670000000300000002000000";
String forwardHeader =
"07466F72776172640849504D2E4E6F7465074D657373616765024657050000000000000000";
String forwardFooter = "0000000000000002000000680000000400000003000000";
String replyToFolderHeader =
"0F5265706C7920746F20466F6C6465720849504D2E506F737404506F737400050000000000000000";
String replyToFolderFooter = "00000000000000020000006C00000008000000";
// Yes, each option goes in TWICE as an ANSI string with no terminator
// https://msdn.microsoft.com/en-us/library/ee218406(v=exchg.80).aspx
StringBuilder optionsAscii = new StringBuilder(2000);
int count = 0;
for (VoteButtonHexifier option : options) {
optionsAscii.append("04000000")
// option (first time)
.append(option.getAsciiHexString())
.append("0849504D2E4E6F746500")
// option (second time)
.append(option.getAsciiHexString())
// internal2 - fixed
.append("00000000"
// internal3 - fixed
+ "00"
// fUseUSHeaders:
// 00000000 = international;
// 01000000 = us
+ "00000000"
// internal4 - fixed
+ "01000000"
// send behavior (01000000 = send immediately,
// 02000000 prompt user to edit or send)
+ "01000000"
// internal5 - fixed: int 0x00000002 (little endian)
+ "02000000"
// ID: record index, 1 based. (little endian)
+ intToLittleEndianString(count++)
// internal 6 (terminator, -1)
+ "ffffffff");
}
// voting option extra bits
String VoteOptionExtras =
"0401055200650070006C00790002520045000C5200650070006C007900200074006F00200041006C006C0002520045000746006F007200770061007200640002460057000F5200650070006C007900200074006F00200046006F006C0064006500720000";
// they are in here in UTF-16LE twice and up above in ASCII twice.
// https://msdn.microsoft.com/en-us/library/ee217598(v=exchg.80).aspx
StringBuilder optionsUtf16Le = new StringBuilder(2000);
for (VoteButtonHexifier option : options) {
// UTF-16LE option (first time)
optionsUtf16Le.append(option.getUtf16LeHexString())
// UTF-16LE option (second time)
.append(option.getUtf16LeHexString());
}
String allowReplyAllVal = "00"; // false
String allowReplyVal = "00"; // false
String allowReplyToFolderVal = "00"; // false
String allowForwardVal = "00"; // false
String VerbValue = header + recordCnt + preReplyAllPadding
+ replyToAllHeader + allowReplyAllVal + replyToAllFooter
+ replyToHeader + allowReplyVal + replyToFooter + forwardHeader
+ allowForwardVal + forwardFooter + replyToFolderHeader
+ allowReplyToFolderVal + replyToFolderFooter + optionsAscii
+ VoteOptionExtras + optionsUtf16Le;
return VerbValue;
}
public static String intToLittleEndianString(int count) {
return String.format("%08x", swapEndianOrder(count));
}
public static int swapEndianOrder(int i) {
return (i & 0xff) << 24
| (i & 0xff00) << 8
| (i & 0xff0000) >> 8
| (i >> 24) & 0xff;
}
}
投票按钮标签需要ASCII和小端UTF16:
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.apache.commons.codec.binary.Hex;
/**
* Accepts a String and produces ASCII and little-endian UTF16
* representations of those string to embed in voting buttons.
* TODO: enhance code to accept separate strings for the ASCII and UTF16
* strings to better support I18Z.
* @author Tim Perry
*/
public class VoteButtonHexifier
implements Comparable<VoteButtonHexifier> {
private String buttonLabel;
public VoteButtonHexifier(String buttonLabel) {
if (buttonLabel == null) {
throw new NullPointerException("buttonLabel may not be null");
}
this.buttonLabel = buttonLabel;
}
/**
* This will return valid ASCII characters IFF the input can be
* represented as ASCII characters. Be careful to sanitize input.
* @return the button label as the hex of UTF_8.
*/
public String getAsciiHexString() {
String buttonLabel = UnicodeCharacterUtils
.fixQuotesElipsesAndHyphens(this.buttonLabel);
String lengthHex = String.format("%02X", buttonLabel.length());
String labelHex = Hex.encodeHexString(
buttonLabel.getBytes(StandardCharsets.UTF_8));
return lengthHex + labelHex;
}
public String getUtf16LeHexString() {
String lengthHex = String.format("%02X", buttonLabel.length());
String labelUtf16_ElHex = utf16LeHex(buttonLabel);
return lengthHex + labelUtf16_ElHex;
}
private String utf16LeHex(String var) {
Charset charset = Charset.forName("UTF-16LE");
return Hex.encodeHexString(var.getBytes(charset));
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof VoteButtonHexifier)) {
return false;
}
VoteButtonHexifier other = (VoteButtonHexifier) o;
return buttonLabel.equals(other.buttonLabel);
}
@Override
public int hashCode() {
return buttonLabel.hashCode();
}
@Override
public int compareTo(VoteButtonHexifier o) {
if (o == null) {
return 1;
}
return buttonLabel.compareTo(o.buttonLabel);
}
}
相关文章