简体   繁体   中英

Microsoft graph how to add Office 365 Voting Buttons to an e-mail Message

I used to be able to add Outlook Voting Buttons to an e-mail sent by Microsoft Exchange Web Services (EWS). But since moving to Microsoft.Graph have been unsuccessful implementing the functionality to send and receive Microsoft.Graph.Message with Outlook 365 Vote Button functionality.

Does anyone have any idea how to implement such functionality?

I've included all the code to send emails with voting buttons using the MS Graph API Java SDK. I have successfully sent 5 emails with it, but I believe there are probably still bugs in it. With that in mind: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

With that out of the way, here is the the code to send an email with voting buttons using the MS Graph API SDK for Java:

    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.
        //
        // 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
        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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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