0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Office365からRedmineへのメール送信してチケット登録

Last updated at Posted at 2023-05-26

ハマったこと

この3日間悩みまくりました。
結論から言うと、メールソフトはメールサーバーで扱え・・・

コード

最初はPOSTでFASTAPIに送ろうとしてたけど、エラー404 not foundが消えず。

もう半ばあきらめた時に、とっさに閃いたのは、結局は前回と同じで
pythonのメールサーバーに送ればいいんじゃない?

方針変えてからコードが動くまでは、あっと言う間でした。

cs.cs
using System;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.Outlook;
using System.Net.Mail;
using Attachment = Microsoft.Office.Interop.Outlook.Attachment;

namespace SampleRibbonOutlookAddIn
{
    [ComVisible(true)]
    public class Ribbon : IRibbonExtensibility
    {
        string REDMINE = "redmine@admin.com";

        public void onMyButton_Click(IRibbonControl control)
        {
            Microsoft.Office.Interop.Outlook.MailItem mailItem = GetSelectedMailItem();
            // Prompt the user to confirm sending to Redmine
            DialogResult result = MessageBox.Show(
                 mailItem.Subject, "Send to Redmine?",
                    MessageBoxButtons.YesNo,
                MessageBoxIcon.Question
            );

            if (result == DialogResult.Yes)   {
                if (mailItem != null)                {
                    // Call the Redmine API function with the email subject as the issue title
                    Post_message3(mailItem);
                }else
                { MessageBox.Show("No MailItem selected!");}
            }
        }

        private void Post_message3(MailItem outlookMessage )   {
            MailMessage message = new MailMessage();
            message.To.Add(new MailAddress(outlookMessage.SenderEmailAddress));
            message.Subject = outlookMessage.Subject;
            message.Body = outlookMessage.Body;
            message.From = new MailAddress(REDMINE);
            foreach (Microsoft.Office.Interop.Outlook.Attachment attachment in outlookMessage.Attachments)
            {
                string displayName = attachment.DisplayName;
                int index = displayName.LastIndexOf('.');
                string extension = index > -1 ? displayName.Substring(index) : string.Empty;
                // Get the MIME content type of the attachment
                string contentType = attachment.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x370E001F");
                message.Attachments.Add(new System.Net.Mail.Attachment(attachment.PathName, contentType));
            }
            // Create a new SmtpClient object
            SmtpClient client = new SmtpClient("172.26.106.79", 1025);
            client.UseDefaultCredentials = false;
            client.Send(message);
        }
    }
}

メール受信(python)

だいぶ、苦労したけど動きました。

ハマったこと

smtpのメッセージから、メール添付ファイルの取り出し(全部、CHATGPTに書かせた)

課題

メールの送信者のアドレスで、redmineに都度ログインする部分が出来ていない
→ まずはadminのTOKENでログインしている

python.py

import smtpd
import asyncore
import email
import os
from email.header import decode_header
from redminelib import Redmine
import pdb
from datetime import datetime, timedelta

SAVE_DIR = "./store"

def save_attachment(msg, save_dir):
    filepaths = []
    for part in msg.walk():
        if part.get_content_maintype() == 'multipart':
            continue
        if part.get('Content-Disposition') is None:
            continue

        filename = part.get_filename()
        if filename:
            # Decode the filename if it's encoded
            decoded_filename = decode_header(filename)[0][0]
            if isinstance(decoded_filename, bytes):
                # Handle bytes strings
                decoded_filename = decoded_filename.decode()

            # Remove any invalid characters from the filename
            cleaned_filename = ''.join(c for c in decoded_filename if c.isalnum() or c in ['.', '_', '-'])

            save_path = os.path.join(save_dir, cleaned_filename)
            with open(save_path, 'wb') as f:
                f.write(part.get_payload(decode=True))

            print(f"Saved attachment: {cleaned_filename}")
            filepaths.append(save_path)

    print("All attachments saved successfully.")
    return filepaths

# Define the function for parsing the email message
def mail_parse(msg):
    email_data = {}
    email_data['From'] = msg['from']
    email_data['To'] = msg['to']
    email_data['Subject'] = msg['subject']
    email_data['Body']=msg['body']

    # Extract body and add to email_data
    for part in msg.walk():
        if part.get_content_type() == 'text/plain':
            body = part.get_payload(decode=True).decode()
            email_data['Body'] = body

    # Save attachments and add their filepaths to email_data
    if msg.get_content_maintype() == 'multipart':
        files = save_attachment(msg, SAVE_DIR)
        email_data['Attachments'] = files

    return email_data

class RedmineWork:
    def __init__(self, url):
        self.url = url
        
    def api_login(self, api_key):
        self.api = Redmine(self.url, key=api_key) 

    def get_all_pjt(self):
        return self.api.project.all()

    def push_ticket(self,msg,files):
        projects = self.get_all_pjt()
        for project in projects:
            print(project.name)
        issue = self.api.issue.new()
# 日付を設定
        start_date = datetime.now().date()
        issue.start_date = start_date
        issue.due_date = start_date + timedelta(days=1) # Add one day to due date

        issue.project_id = projects[0].id 
        issue.subject = msg["Subject"]
        issue.description = ""
        issue.tracker_id = 1#新規
        issue.status_id = 1
        issue.priority_id = 1
        issue.assigned_to_id = 1
        files_up = [{'path': file_path,'filename': os.path.basename(file_path)} for file_path in files]
        issue.uploads = files_up

        issue.save()

class CustomSMTPServer(smtpd.SMTPServer):
    def __init__(self, localaddr, remoteaddr, redmine, **kwargs):
        self.redmine = redmine
        super().__init__(localaddr, remoteaddr, **kwargs)

    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        msg = email.message_from_bytes(data)
        files = save_attachment(msg, SAVE_DIR)
        self.redmine.push_ticket(msg,files)

def start_server(url, api_key):
    redmine = RedmineWork(url)
    redmine.api_login(api_key)

    # Create an instance of the custom SMTP server with redmine passed as argument
    server = CustomSMTPServer(('localhost', 1025), None, redmine)
    asyncore.loop()

if __name__ == "__main__":
    url = r"http://redmine:8000"
    api_key = "9e9c6a437feffbd06dc070e348f60e9eb1b7f027"

    start_server(url, api_key)

参考

  • Redmineに関連する記事
  1. 仕事で使うREMINEに関する考察(19)
  2. WIKI一発記入 仕事で使うREDMINEに関する考察(18)
  3. CHATGPTにてRedmine view customizerのHTML要素を生成する(4)
  4. チケット運用の盲点 仕事で使うREDMINEに関する考察(17)
  5. CHATGPTにてRedmine view customizerのHTML要素を生成する(3)
  6. CHATGPTにてRedmine view customizerのHTML要素を生成する(2)
  7. CHATGPTにてRedmine view customizerのHTML要素を生成する(1)
  8. CHATGPT便りの開発方針 仕事で使うREDMINEに関する考察(16)
  9. チケットからTODO作業へ落とし込み 仕事で使うREDMINEに関する考察(15)
  10. 仕事で使うREDMINEに関する考察(14)
  11. CHATGPT利活用 仕事で使うREDMINEに関する考察(12)
  12. やりたいことを少しずつ 仕事で使うREDMINEに関する考察(11)
  13. 組織を巻き込むプレゼン資料 仕事で使うREDMINEに関する考察(10)
  14. OfficeのフローにREDMINEをねじ込む 仕事で使うREDMINEに関する考察(9)
  15. RedmineチケットにCHATGPTを実装(超簡単)
  16. RedmineのチケットにCHATGPTを実装(2)
  17. Office365からRedmineへのメール送信してチケット登録
  18. Redmineプラグイン開発
  19. RedmineをTODOリストに使う 仕事で使うREDMINEに関する考察(8)
  20. 仕事で使うREDMINEに関する考察(7)
  21. 仕事で使うREDMINEに関する考察(6)
  22. 仕事で使うREDMINEに関する考察(5)
  23. 仕事で使うREDMINEに関する考察(4)
  24. 仕事で使うREDMINEに関する考察(3)
  25. 仕事で使うREDMINEに関する考察(2)
  26. 仕事で使うREDMINEに関する考察
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?