3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【自分用】Microsoft Teamsのチャットログを無理やり保存する (自動スクロールしながら画面キャプチャするツールつくってみた)

Last updated at Posted at 2020-12-06

まえがき

いろいろやってみたが、画面キャプチャしかやりようがなさそうだったので、やってみた。
(ライセンスによってはTeamsに保存機能があるっぽい??)

使用にあたって

本プログラムはマウス入力を自動で操作するので、非常にキケンなしろものです。
安易に使用せず、プログラムおよびソース等の使用は自己責任でお願いします。

画像のロード等を待たない(一定時間ごとにスクロールさせる)ので、データが正しく取り込めないケースがありえます。

マルチスクリーンでうまく動作するか未確認

スクリーンショット/使い方

image.png

■各コントロール
数値:SendInputへ指定するマウススクロールの量(単位不明。負数にすることもできます。)
Start Injectionボタン:ボタンを押すと、約3秒後にスクロールと画面キャプチャ&保存を開始します。
Stop(Abort)ボタン:ボタンを押すと処理を停止します。※これを押すまで止まりません。

■使い方手順
0-a. 注意事項と手順を読む。
0-b. ネットワークを遮断しておく or チャットやメールなどの割り込みが入らない時間帯で作業する。
0-c. すぐに停止できるように、停止させるまで目を離さないこと!

  1. Teamsを立ち上げておき、キャプチャしたいログを開いておく。
  2. 本プログラムを起動する。
  3. 数値を適宜調整する(試しに使ってみてから変更する)。
  4. Start Injectionボタンを押す。
  5. Teamsのログがマウスのホイール操作でログがスクロールする状態にする(ログに操作フォーカスを移す)。
  6. スクロールし終わるのを待つ。
  7. Stop(Abort)ボタンを押す。
  8. 本プログラムを終了する。
  9. 出力されたファイルを確認する。
  10. 適宜手順0からやり直し
  11. 別途トリミングツールを使って必要な箇所だけ取り出す

ソースコード

CopyTeamsChatAsImg.cs


using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;


// KeyValueWithUpDown
public class KeyUD
{
    public enum Stroke{
        Down,
        Up
    };

    public Keys Key{get;private set;}
    public Stroke KeyStroke{get;private set;} // true: down,   false: up

    public KeyUD(Keys key, Stroke keyStroke) {
        Key = key;
        KeyStroke = keyStroke;
    }
}

class NativeUtil
{
    private static class NativeMethods
    {
        [DllImport("user32.dll", SetLastError = true)]
        public static extern void SendInput(int nInputs, Input[] pInputs, int cbsize);

        [DllImport("user32.dll", EntryPoint = "MapVirtualKeyA")]
        public static extern int MapVirtualKey(int wCode, int wMapType);

        //[DllImport("user32.dll", SetLastError = true)]
        //public extern static IntPtr GetMessageExtraInfo();


        [DllImport("user32.dll")]
        public static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("gdi32.dll")]
        public static extern int BitBlt(IntPtr hDestDC,
            int x,
            int y,
            int nWidth,
            int nHeight,
            IntPtr hSrcDC,
            int xSrc,
            int ySrc,
            int dwRop
        );

        [DllImport("user32.dll")]
        public static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
        
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowDC(IntPtr hwnd);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        public static extern int GetWindowRect(IntPtr hwnd, ref  RECT lpRect);

        
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetProcessDPIAware();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT 
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MouseInput
    {
        public int X;
        public int Y;
        public int Data;
        public int Flags;
        public int Time;
        public IntPtr ExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct KeyboardInput
    {
        public short VirtualKey;
        public short ScanCode;
        public int Flags;
        public int Time;
        public IntPtr ExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct HardwareInput
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct Input
    {
        public int Type;
        public InputUnion ui;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct InputUnion
    {
        [FieldOffset(0)]
        public MouseInput Mouse;
        [FieldOffset(0)]
        public KeyboardInput Keyboard;
        [FieldOffset(0)]
        public HardwareInput Hardware;
    }

    private const int KEYEVENTF_EXTENDEDKEY = 0x0001;
    private const int KEYEVENTF_KEYUP = 0x0002;
    private const int KEYEVENTF_SCANCODE = 0x0008;
    private const int KEYEVENTF_UNICODE = 0x0004;

    private const int MAPVK_VK_TO_VSC = 0;
    // private const int MAPVK_VSC_TO_VK = 1;


    public static void SendInputKeys(KeyUD[] keys)
    {
        Input[] inputs = new Input[keys.Length];

        for(int k=0; k<keys.Length; k++) {
            int vsc = NativeMethods.MapVirtualKey((int)keys[k].Key, MAPVK_VK_TO_VSC);

            inputs[k] = new Input();
            inputs[k].Type = 1; // KeyBoard = 1
            inputs[k].ui.Keyboard.VirtualKey = (short)keys[k].Key;
            inputs[k].ui.Keyboard.ScanCode = (short)vsc;
            inputs[k].ui.Keyboard.Flags = (keys[k].KeyStroke==KeyUD.Stroke.Down)?0:KEYEVENTF_KEYUP;
            inputs[k].ui.Keyboard.Time = 0;
            inputs[k].ui.Keyboard.ExtraInfo = IntPtr.Zero;
        }

        NativeMethods.SendInput(inputs.Length, inputs, Marshal.SizeOf(inputs[0]));
    }


    //private const int MOUSEEVENTF_MOVE        = 0x0001;
    //private const int MOUSEEVENTF_LEFTDOWN    = 0x0002;
    //private const int MOUSEEVENTF_LEFTUP      = 0x0004;
    //private const int MOUSEEVENTF_VIRTUALDESK = 0x4000;
    //private const int MOUSEEVENTF_ABSOLUTE    = 0x8000;
    private const int MOUSEEVENTF_WHEEL = 0x0800;
    
    private static Input MakeMouseWheelData(int amountOfMove, IntPtr extraInfo)
    {
        Input input = new Input();
        input.Type = 0; // MOUSE = 0
        input.ui.Mouse.Flags = MOUSEEVENTF_WHEEL;
        input.ui.Mouse.Data = amountOfMove;
        input.ui.Mouse.X = 0;
        input.ui.Mouse.Y = 0;
        input.ui.Mouse.Time = 0;
        input.ui.Mouse.ExtraInfo = extraInfo;
        return input;
    }

    public static void SendMouseWheel(int amountOfMove)
    {
        Input[] inputs = new Input[]{MakeMouseWheelData(amountOfMove, IntPtr.Zero)};
        NativeMethods.SendInput(inputs.Length, inputs, Marshal.SizeOf(inputs[0]));
    }


    private const int SRCCOPY = 13369376;
    private const int CAPTUREBLT = 1073741824;

    public static Bitmap CaptureActiveWindow()
    {
        //アクティブなウィンドウのデバイスコンテキストを取得
        IntPtr hWnd = NativeMethods.GetForegroundWindow();
        IntPtr winDC = NativeMethods.GetWindowDC(hWnd);

        if (hWnd==IntPtr.Zero){return null;}

        //ウィンドウの大きさを取得
        RECT winRect = new RECT();
        NativeMethods.GetWindowRect(hWnd, ref winRect);
        Bitmap bmp = new Bitmap(winRect.right - winRect.left, winRect.bottom - winRect.top);
        Graphics g = Graphics.FromImage(bmp);
        //Graphicsのデバイスコンテキストを取得
        IntPtr hDC = g.GetHdc();
        //Bitmapに画像をコピーする
        NativeMethods.BitBlt(hDC, 0, 0, bmp.Width, bmp.Height, winDC, 0, 0, SRCCOPY);
        g.ReleaseHdc(hDC);
        g.Dispose();
        NativeMethods.ReleaseDC(hWnd, winDC);

        return bmp;
    }

    public static bool SetProcessDPIAware()
    {
        return NativeMethods.SetProcessDPIAware();
    }
}


class CopyTeamsChat : Form
{
    Button btnStart;
    Button btnAbort;
    NumericUpDown nudMouseScroll;

    System.Windows.Forms.Timer tmr;
    int _countForStartDelay;
    bool _abortReq;
    bool _timerIsRunning;
    List<MemoryStream> _msBuffer;
    readonly int IntervalInMs   = 300;
    readonly int StartDelayInMs = 3000;
    int _countOfSavedImage;

    CopyTeamsChat()
    {
        NativeUtil.SetProcessDPIAware();

        _countForStartDelay = 0;
        _abortReq = false;
        _timerIsRunning = false;
        _countOfSavedImage = 0;

        tmr = new System.Windows.Forms.Timer();
        tmr.Interval = IntervalInMs;
        tmr.Tick += (s,e)=>{
            if ( _abortReq ) {
                // 停止させる
                tmr.Stop();
                btnStart.Enabled = true;
                //btnSave.Enabled = (_msBuffer.Count>0);
                nudMouseScroll.Enabled = true;
                _abortReq = false;
                _timerIsRunning = false;
                return;
            }
            
            if ( _countForStartDelay > 0 ) {
                _countForStartDelay--;
                int inMsecForShowingCountDown = _countForStartDelay * IntervalInMs;
                Text = (inMsecForShowingCountDown/1000).ToString() + "." + ((inMsecForShowingCountDown%1000)/100).ToString();
            }
            else{
                tmr.Stop();
                NativeUtil.SendMouseWheel((int)nudMouseScroll.Value);

                Screen curScreen =  Screen.FromControl(this);
                Bitmap bmp = new Bitmap(curScreen.Bounds.Width, curScreen.Bounds.Height);
                Graphics g = Graphics.FromImage(bmp);
                //画面全体をコピーする
                g.CopyFromScreen(new Point(curScreen.Bounds.Left, curScreen.Bounds.Top), new Point(0, 0), bmp.Size);
                g.Dispose();

                bmp.Save(@"img/TeamsCapture" + _countOfSavedImage.ToString().PadLeft(6, '0') + ".png", System.Drawing.Imaging.ImageFormat.Png);
                _countOfSavedImage++;

                Text = "SavedCount:" + _countOfSavedImage.ToString();
                tmr.Start();
            }
        };

        nudMouseScroll = new NumericUpDown(){
            Location = new Point(0,0),
            Width = 100,
            Maximum = 1000,
            Value = 200,
            Minimum = -1000,
        };
        Controls.Add(nudMouseScroll);

        btnStart = new Button(){
            Location = new Point(0,40),
            Size = new Size(150,30),
            Text = "Start Injection",
        };
        btnStart.Click += (s,e)=>{
            btnStart.Enabled = false;
            //btnSave.Enabled = false;
            nudMouseScroll.Enabled = false;
            if ( !_timerIsRunning ) {
                _msBuffer = new List<MemoryStream>();
                _timerIsRunning = true;
                _countForStartDelay = StartDelayInMs/IntervalInMs;
                tmr.Start();
            }
        };
        Controls.Add(btnStart);


        btnAbort = new Button(){
            Location = new Point(0,80),
            Size = new Size(150,30),
            Text = "Stop(Abort)",
        };
        btnAbort.Click += (s,e)=>{
            _abortReq = true;
        };
        Controls.Add(btnAbort);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new CopyTeamsChat());
    }
}

参考サイト

3
4
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
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?