简体   繁体   English

使用 MS Exchange Graph API Java SDK 发送带有投票按钮的交换电子邮件

[英]Send exchange email with voting buttons using MS Exchange Graph API Java SDK

How do you send emails with voting buttons using the Graph API?如何使用 Graph API 发送带有投票按钮的电子邮件? I know how to do this using EWS, but I can't find anything describing how to do this using the Graph API.我知道如何使用 EWS 执行此操作,但找不到任何描述如何使用 Graph API 执行此操作的内容。

I've included all the code to send emails with voting buttons using the MS Graph API Java SDK.我已经包含了使用 MS Graph API Java SDK 发送带有投票按钮的电子邮件的所有代码。 The voting button labels are sent in a SingleValueLegacyExtendedProperty.投票按钮标签在 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();

Voting button encoder:投票按钮编码器:

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;
    }

}

You need both ASCII and little-endian UTF16 for the voting button labels:投票按钮标签需要 ASCII 和 little-endian 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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM