├── .gitattributes
├── .github
└── workflows
│ ├── clear.yml
│ ├── cli.yml
│ ├── linux-dida.yml
│ └── linux.yml
├── .gitignore
├── Directory.Build.props
├── LICENSE.txt
├── README.md
├── Settings.XamlStyler
├── TodoSynchronizer.CLI
├── AesHelper.cs
├── ConsoleAdapter.cs
├── ISimpleLogger.cs
├── LogFileAdapter.cs
├── OfflineTokenDto.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── RefreshModel.cs
└── TodoSynchronizer.CLI.csproj
├── TodoSynchronizer.Core
├── Config
│ └── SyncConfig.cs
├── Extensions
│ ├── EmojiExtension.cs
│ ├── HtmlExtension.cs
│ ├── ListExtension.cs
│ └── UrlExtension.cs
├── Helpers
│ ├── CanvasPreference.cs
│ ├── CanvasStringTemplateHelper.cs
│ ├── Common.cs
│ ├── HtmlHelper.cs
│ ├── LargeFileUploadTask.cs
│ ├── UploadResponseHandler.cs
│ └── UploadSliceRequest.cs
├── Models
│ ├── CanvasModels
│ │ ├── Anouncement.cs
│ │ ├── Assignment.cs
│ │ ├── AssignmentSubmission.cs
│ │ ├── CanvasLoginResult.cs
│ │ ├── Common.cs
│ │ ├── Course.cs
│ │ ├── Discussion.cs
│ │ ├── ICanvasItem.cs
│ │ ├── Notification.cs
│ │ ├── Quiz.cs
│ │ ├── QuizSubmission.cs
│ │ ├── SubmissionComment.cs
│ │ └── User.cs
│ ├── CommonResult.cs
│ ├── DidaModels
│ │ ├── DidaBatchCheckDto.cs
│ │ ├── DidaBatchDto.cs
│ │ ├── DidaCheckItem.cs
│ │ ├── DidaTask.cs
│ │ ├── DidaTaskComment.cs
│ │ ├── DidaTaskList.cs
│ │ └── LoginDto.cs
│ ├── MyReadOnlySubStream.cs
│ ├── SyncState.cs
│ └── WebResult.cs
├── Services
│ ├── CanvasService.cs
│ ├── DidaService.cs
│ ├── DidaSyncService.cs
│ ├── SyncService.cs
│ ├── TodoService.cs
│ └── Web.cs
├── TodoSynchronizer.Core.csproj
└── Yaml
│ └── IgnoreCaseTypeInspector.cs
├── TodoSynchronizer.QuickTool
├── App.config
├── App.xaml
├── App.xaml.cs
├── Broker
│ ├── DefaultOsBrowserWebUi.cs
│ ├── HttpListenerInterceptor.cs
│ ├── IUriInterceptor.cs
│ └── MessageAndHttpCode.cs
├── Helpers
│ ├── AesHelper.cs
│ ├── HardwareAcceleration.cs
│ └── WordHelper.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Pages
│ ├── Page1.xaml
│ ├── Page1.xaml.cs
│ ├── Page2.xaml
│ ├── Page2.xaml.cs
│ ├── Page3.xaml
│ ├── Page3.xaml.cs
│ ├── Page4.xaml
│ └── Page4.xaml.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Services
│ ├── DataService.cs
│ ├── NaviService.cs
│ ├── RenderingTier.cs
│ ├── TransitionService.cs
│ └── TransitionType.cs
├── TodoSynchronizer.QuickTool.csproj
└── key.svg
├── TodoSynchronizer.sln
├── TodoSynchronizer
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Controls
│ ├── ClippingBorder.cs
│ └── SideHideBar.cs
├── Converters
│ ├── BoolToAppearanceConverter.cs
│ ├── BorderClipConverter.cs
│ └── RevBooleanToVisibilityConverter.cs
├── Extensions
│ └── ClickExt.cs
├── Helpers
│ ├── AnimationHelper.cs
│ ├── BitmapHelper.cs
│ ├── IniHelper.cs
│ ├── MsalHelper.cs
│ └── TransformHelper.cs
├── Models
│ └── LoginInfo.cs
├── Mvvm
│ └── RoutedEventTrigger.cs
├── Properties
│ └── launchSettings.json
├── Resources
│ ├── Icons.xaml
│ ├── cloud.svg
│ ├── todo.svg
│ └── todosync.ico
├── Styles
│ └── SideHideBar.xaml
├── TodoSynchronizer.csproj
├── UnitTest
│ ├── CanvasTestWindow.xaml
│ ├── CanvasTestWindow.xaml.cs
│ ├── Styles
│ │ ├── CanvasTemplate.xaml
│ │ ├── CanvasTemplateSelector.cs
│ │ ├── TodoListBoxStyle.xaml
│ │ ├── TodoTemplate.xaml
│ │ └── TodoTemplateSelector.cs
│ ├── TodoTestWindow.xaml
│ └── TodoTestWindow.xaml.cs
├── ViewModels
│ ├── CanvasLoginViewModel.cs
│ └── TodoLoginViewModel.cs
└── Views
│ ├── CanvasLoginWindow.xaml
│ ├── CanvasLoginWindow.xaml.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── Pages
│ ├── AnouncementPage.xaml
│ ├── AnouncementPage.xaml.cs
│ ├── AssignmentPage.xaml
│ ├── AssignmentPage.xaml.cs
│ ├── DiscussionPage.xaml
│ ├── DiscussionPage.xaml.cs
│ ├── GeneralPage.xaml
│ ├── GeneralPage.xaml.cs
│ ├── QuizPage.xaml
│ ├── QuizPage.xaml.cs
│ ├── UIPage.xaml
│ └── UIPage.xaml.cs
│ ├── SettingsWindow.xaml
│ └── SettingsWindow.xaml.cs
├── config.yaml
├── docs
├── actions-persis.md
├── graph-token-manually.md
└── local.md
└── files
├── canvas.svg
├── todosync-512.png
├── todosync-final.png
├── todosync-final.svg
├── todosync-head.png
├── todosync-head.psd
├── todosync-main.png
├── todosync-main.sketch
├── todosync.sketch
├── todosync.svg
├── user-round.svg
└── user.svg
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/clear.yml:
--------------------------------------------------------------------------------
1 | name: Delete old workflow runs
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | days:
6 | description: 'Number of days.'
7 | required: true
8 | default: 30
9 | minimum_runs:
10 | description: 'The minimum runs to keep for each workflow.'
11 | required: true
12 | default: 6
13 | delete_workflow_pattern:
14 | description: 'The name or filename of the workflow. if not set then it will target all workflows.'
15 | required: false
16 | delete_workflow_by_state_pattern:
17 | description: 'Remove workflow by state: active, deleted, disabled_fork, disabled_inactivity, disabled_manually'
18 | required: true
19 | default: "All"
20 | type: choice
21 | options:
22 | - "All"
23 | - active
24 | - deleted
25 | - disabled_inactivity
26 | - disabled_manually
27 | delete_run_by_conclusion_pattern:
28 | description: 'Remove workflow by conclusion: action_required, cancelled, failure, skipped, success'
29 | required: true
30 | default: "All"
31 | type: choice
32 | options:
33 | - "All"
34 | - action_required
35 | - cancelled
36 | - failure
37 | - skipped
38 | - success
39 | dry_run:
40 | description: 'Only log actions, do not perform any delete operations.'
41 | required: false
42 |
43 | jobs:
44 | del_runs:
45 | runs-on: ubuntu-latest
46 | steps:
47 | - name: Delete workflow runs
48 | uses: Mattraks/delete-workflow-runs@v2
49 | with:
50 | token: ${{ github.token }}
51 | repository: ${{ github.repository }}
52 | retain_days: ${{ github.event.inputs.days }}
53 | keep_minimum_runs: ${{ github.event.inputs.minimum_runs }}
54 | delete_workflow_pattern: ${{ github.event.inputs.delete_workflow_pattern }}
55 | delete_workflow_by_state_pattern: ${{ github.event.inputs.delete_workflow_by_state_pattern }}
56 | delete_run_by_conclusion_pattern: ${{ github.event.inputs.delete_run_by_conclusion_pattern }}
57 | dry_run: ${{ github.event.inputs.dry_run }}
58 |
--------------------------------------------------------------------------------
/.github/workflows/cli.yml:
--------------------------------------------------------------------------------
1 | name: Build Local App
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 | build:
6 | name: ${{ matrix.os }}
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | os: [windows-latest, ubuntu-latest]
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v2
18 | with:
19 | dotnet-version: 6.0.x
20 |
21 | - name: Build for Windows x86/x64
22 | if: matrix.os == 'windows-latest'
23 | run: |
24 | dotnet restore TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj
25 | dotnet build TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj -c Release -f net6.0 --no-restore
26 |
27 | - name: Build for Linux x86/x64
28 | if: matrix.os == 'ubuntu-latest'
29 | run: |
30 | dotnet restore TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj
31 | dotnet build TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj -c Release -f net6.0 --no-restore
32 |
33 | - name: Build for Linux ARM
34 | if: matrix.os == 'ubuntu-latest'
35 | run: |
36 | dotnet restore TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj
37 | dotnet build TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj -c Release -f net6.0 -a arm64 --no-restore
38 |
39 | - name: Upload Build Artifacts
40 | uses: actions/upload-artifact@v3.1.0
41 | with:
42 | name: ${{ matrix.os }}
43 | if-no-files-found: error
44 | path: |
45 | TodoSynchronizer.CLI/bin
46 |
--------------------------------------------------------------------------------
/.github/workflows/linux-dida.yml:
--------------------------------------------------------------------------------
1 | name: 同步到滴答清单
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 0-14/1,22,23 * * *'
7 |
8 | jobs:
9 | sync:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Setup .NET
16 | uses: actions/setup-dotnet@v2
17 | with:
18 | dotnet-version: 6.0.x
19 | - name: Restore dependencies
20 | run: dotnet restore ./TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj
21 | - name: Build
22 | run: dotnet build ./TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj --no-restore -c Release
23 | - name: Write Dida Credential
24 | run: echo '${{ secrets.DIDA_CREDENTIAL }}' > dida.json
25 | - name: Run
26 | run: ./TodoSynchronizer.CLI/bin/Release/net6.0/TodoSynchronizer.CLI -canvastoken ${{ secrets.CANVAS_TOKEN }} -configfile config.yaml -didacredentialfile dida.json
27 |
--------------------------------------------------------------------------------
/.github/workflows/linux.yml:
--------------------------------------------------------------------------------
1 | name: 同步到 MS Todo
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 0-14/1,22,23 * * *'
7 |
8 | jobs:
9 | sync:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Branch Filestorage Action
16 | uses: moonrailgun/branch-filestorage-action@v1.2.2
17 | with:
18 | branch: graphtoken
19 | path: graphtoken.asc
20 | - name: Setup .NET
21 | uses: actions/setup-dotnet@v2
22 | with:
23 | dotnet-version: 6.0.x
24 | - name: Restore dependencies
25 | run: dotnet restore ./TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj
26 | - name: Build
27 | run: dotnet build ./TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj --no-restore -c Release
28 | - name: Run
29 | run: ./TodoSynchronizer.CLI/bin/Release/net6.0/TodoSynchronizer.CLI -canvastoken ${{ secrets.CANVAS_TOKEN }} -graphtokenfile graphtoken.asc -configfile config.yaml -graphtokenkey ${{ secrets.KEY }}
30 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 10.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## 程序简介
4 | 基于 GitHub Actions 的定时任务(每小时运行一次同步),将 Canvas LMS 的作业、测验、公告、讨论同步到 Microsoft Todo/滴答清单(目前仅适配上海交通大学 Canvas 系统,理论上所有 Canvas LMS 都能用)
5 |
6 | 
7 | 
8 |
9 | ## 使用方法
10 | - GitHub Actions 方式:[部署指南](/docs/actions-persis.md)
11 | - 本地运行方式:[部署指南](/docs/local.md)
12 |
13 | ## 特别感谢
14 | - [microsoftgraph/msgraph-sdk-dotnet](https://github.com/microsoftgraph/msgraph-sdk-dotnet)
15 | - [aaubry/YamlDotNet](https://github.com/aaubry/YamlDotNet)
16 | - [moonrailgun/branch-filestorage-action](https://github.com/moonrailgun/branch-filestorage-action)
17 | - [lepoco/wpfui](https://github.com/lepoco/wpfui)
18 | - [TDK1969/nonebot_plugin_dida](https://github.com/TDK1969/nonebot_plugin_dida)
19 |
20 | ## 说在最后
21 | 如果觉得程序好用的话,请点亮右上角的 Star 哦~
22 |
23 | 以及,欢迎Bug Report & Pull Request
--------------------------------------------------------------------------------
/Settings.XamlStyler:
--------------------------------------------------------------------------------
1 | {
2 | // ==========[属性格式化]==========
3 | "AttributesTolerance": 2, // 单行最大属性数,2[默认],如果元素属性数不大于此数就不会换行
4 | "KeepFirstAttributeOnSameLine": true, // 第一个属性是否与开始标记在同一行,false[默认]
5 | "MaxAttributeCharactersPerLine": 60, // 多个属性大于多少个字符就该换行,0[默认]
6 | "MaxAttributesPerLine": 2, // 大于几个属性就该换行,1[默认]
7 | // 无需换行的元素(比较短的),"RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter"[默认]
8 | "NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
9 | "AttributeIndentation": 0, // 属性缩进空格字符数(-1不缩进;0[默认]缩进4个空格;其它个数则指定)
10 | "AttributeIndentationStyle": 1, // 属性缩进风格(0混合,视情况使用制表符和空格;1[默认]使用空格)
11 | "RemoveDesignTimeReferences": false, // 是否移除自动添加的控件和设计时参考内容,false[默认]
12 | //
13 | // ==========[属性排序]==========
14 | "EnableAttributeReordering": true, // 是否启用属性的自动排序,true[默认]
15 | "SeparateByGroups": true, // 是否应该按照属性的分组进行分隔,false[默认]
16 | /* 属性排序和分组规则
17 | */
18 | "AttributeOrderingRuleGroups": [
19 | "x:Class",
20 | "xmlns, xmlns:x",
21 | "xmlns:*",
22 | "x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
23 | "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight,MaxLength",
24 | "HorizontalAlignment, VerticalAlignment",
25 | "TextWrapping, Margin, Padding",
26 | "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
27 | "Background, Foreground",
28 | "Content,Text,IsChecked",
29 | "Command,CommandParameter",
30 | "HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
31 | "*:*, *",
32 | "FontSize, FontFamily",
33 | "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
34 | "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
35 | "Storyboard.*, From, To, Duration"
36 | ],
37 | "FirstLineAttributes": "x:Name", // 应该在第一行的属性,例如Name、x:Name和x:Uid等等,None[默认]
38 | "OrderAttributesByName": false, // 是否按照属性名称进行排序
39 | //
40 | // ==========[元素格式化]==========
41 | "PutEndingBracketOnNewLine": false, // 结束括号是否独占一行,false[默认]
42 | "RemoveEndingTagOfEmptyElement": true, // 是否移除空元素的结束标签,true[默认]
43 | "SpaceBeforeClosingSlash": true, // 自闭合元素的末尾斜杠前是否要有空格,true[默认]
44 | "RootElementLineBreakRule": 0, // 是否将根元素的属性分成多行(0[默认]默认;1始终;2从不)
45 | //
46 | // ==========[元素排序]==========
47 | "ReorderVSM": 2, // 是否重新排序Visual State Manager(0未定义;1[默认]移到最后;2移到最前)
48 | "ReorderGridChildren": false, // 是否重新排序Grid的子元素,false[默认]
49 | "ReorderCanvasChildren": false, // 是否重新排序Canvas的子元素,false[默认]
50 | "ReorderSetters": 0, // 是否重新排序Setter(0[默认]不排序;1按属性名;2按目标名;3先按目标名再按属性名)
51 | //
52 | // ==========[标记扩展]==========
53 | "FormatMarkupExtension": true, // 是否格式化标记扩展的属性,true[默认]
54 | // 始终放在一行上的标记扩展,"x:Bind, Binding"[默认]
55 | "NoNewLineMarkupExtensions": "x:Bind, Binding",
56 | //
57 | // ==========[属性排序]==========
58 | "ThicknessSeparator": 2, // Thickness类型的属性应该用哪种分隔符(0不格式化;1空格;2[默认]逗号)
59 | // 被认定为Thickness的元素应该是哪些,"Margin, Padding, BorderThickness, ThumbnailClipMargin"[默认]
60 | "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
61 | //
62 | // ==========[杂项]==========
63 | "FormatOnSave": false, // 是否在保存时进行格式化,true[默认]
64 | "CommentPadding": 2, // 注释的间距应该是几个空格,2[默认]
65 | //
66 | // ==========[覆盖VS配置]==========
67 | "IndentSize": 4, // 缩进空格数,4[VS默认]
68 | "IndentWithTabs": false // 是否使用制表符进行缩进,false[VS默认]
69 | }
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/ConsoleAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.CLI
8 | {
9 | public class ConsoleAdapter : ISimpleLogger
10 | {
11 | public void Log(string message)
12 | {
13 | Console.WriteLine(message);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/ISimpleLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.CLI
8 | {
9 | public interface ISimpleLogger
10 | {
11 | void Log(string message);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/LogFileAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.CLI
8 | {
9 | public class LogFileAdapter : ISimpleLogger
10 | {
11 | FileStream fs;
12 | StreamWriter sw;
13 | public LogFileAdapter(string filename)
14 | {
15 | fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
16 | sw = new StreamWriter(fs);
17 | }
18 | public void Log(string message)
19 | {
20 | sw.WriteLine(message);
21 | sw.Flush();
22 | fs.Flush();
23 | }
24 |
25 | ~LogFileAdapter() { sw.Close();fs.Close(); }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/OfflineTokenDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.CLI
8 | {
9 | public class OfflineTokenDto
10 | {
11 | public string CanvasToken { get; set; }
12 | public string GraphToken { get; set; }
13 | public DidaCredential DidaCredential { get; set; }
14 | }
15 |
16 | public class DidaCredential
17 | {
18 | public string phone { get; set; }
19 | public string password { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TodoSynchronizer.CLI": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-local"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/RefreshModel.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.CLI
9 | {
10 | public partial class RefreshModel
11 | {
12 | [JsonProperty("access_token")]
13 | public string AccessToken { get; set; }
14 |
15 | [JsonProperty("expires_in")]
16 | public long ExpiresIn { get; set; }
17 |
18 | [JsonProperty("refresh_token")]
19 | public string RefreshToken { get; set; }
20 |
21 | [JsonProperty("scope")]
22 | public string Scope { get; set; }
23 |
24 | [JsonProperty("token_type")]
25 | public string TokenType { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TodoSynchronizer.CLI/TodoSynchronizer.CLI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | disable
8 | win-x86;linux-x86;linux-arm64
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Extensions/EmojiExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Extensions
8 | {
9 | public static class EmojiExtension
10 | {
11 | public static string CleanEmoji(this string str)
12 | {
13 | return Clean2(Clean1(str));
14 | }
15 |
16 | public static string Clean1(string str)
17 | {
18 | foreach (var a in str)
19 | {
20 | byte[] bts = Encoding.UTF32.GetBytes(a.ToString());
21 |
22 | if (bts[0].ToString() == "253" && bts[1].ToString() == "255")
23 | {
24 | str = str.Replace(a.ToString(), "");
25 | }
26 |
27 | }
28 | return str.Trim();
29 | }
30 |
31 | public static string Clean2(string str)
32 | {
33 | StringBuilder sb = new StringBuilder(str.Length);
34 | foreach (var a in str)
35 | {
36 | if (char.GetUnicodeCategory(a) == System.Globalization.UnicodeCategory.OtherSymbol || char.GetUnicodeCategory(a) == System.Globalization.UnicodeCategory.Surrogate)
37 | {
38 |
39 | }
40 | else
41 | sb.Append(a);
42 |
43 | }
44 | return sb.ToString().Trim();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Extensions/HtmlExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Extensions
8 | {
9 | public static class HtmlExtension
10 | {
11 | ///
12 | /// Html to Esc
13 | ///
14 | /// input
15 | ///
16 | public static string HtmlToEsc(this string input)
17 | {
18 | if (string.IsNullOrEmpty(input)) { return ""; }
19 |
20 | input = input.Replace("&", "&")
21 | .Replace("'", "'")
22 | .Replace("\"", """)
23 | .Replace("<", "<")
24 | .Replace(">", ">")
25 | .Replace(" ", " ")
26 | .Replace("©", "©")
27 | .Replace("®", "®")
28 | .Replace("™", "™");
29 | return input;
30 | }
31 |
32 | ///
33 | /// Esc to Html
34 | ///
35 | /// input
36 | ///
37 | public static string EscToHtml(this string input)
38 | {
39 | if (string.IsNullOrEmpty(input)) { return ""; }
40 |
41 | input = input.Replace("™", "™")
42 | .Replace("®", "®")
43 | .Replace("©", "©")
44 | .Replace(" ", " ")
45 | .Replace(">", ">")
46 | .Replace("<", "<")
47 | .Replace(""", "\"")
48 | .Replace("'", "'")
49 | .Replace("&", "&");
50 | return input;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Extensions/ListExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Extensions
8 | {
9 | public static class ListExtension
10 | {
11 | public static List Shuffle(this List TList)
12 | {
13 | List NewList = new List();
14 | Random Rand = new Random();
15 | foreach (var item in TList)
16 | {
17 | NewList.Insert(Rand.Next(NewList.Count()), item);
18 | }
19 | return (NewList);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Extensions/UrlExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Extensions
9 | {
10 | public static class UrlExtension
11 | {
12 | public static string UrlEscape(this string url)
13 | {
14 | return Uri.EscapeDataString(url);
15 | }
16 | public static string UrlUnescape(this string url)
17 | {
18 | return Uri.UnescapeDataString(url);
19 | }
20 | public static string Connect(this IEnumerable iterator,string separator)
21 | {
22 | return string.Join(separator, iterator);
23 | }
24 |
25 | public static string UrlEncodeByParts(this string url)
26 | {
27 | return url.Split('/')
28 | .Select(s => s.UrlUnescape())
29 | .Connect("/");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Helpers/Common.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Helpers
8 | {
9 | public class Common
10 | {
11 | public static string GetRandomString(int length, bool useNum, bool useLow, bool useUpp, bool useSpe, string custom)
12 | {
13 | Random r = new Random();
14 | string s = null, str = custom;
15 | if (useNum == true) { str += "0123456789"; }
16 | if (useLow == true) { str += "abcdefghijklmnopqrstuvwxyz"; }
17 | if (useUpp == true) { str += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
18 | if (useSpe == true) { str += "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; }
19 | for (int i = 0; i < length; i++)
20 | {
21 | s += str.Substring(r.Next(0, str.Length - 1), 1);
22 | }
23 | return s;
24 |
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Helpers/UploadSliceRequest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graph;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net.Http;
5 | using System.Net.Http.Headers;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace TodoSynchronizer.Core.Helpers
10 | {
11 | ///
12 | /// The UploadSliceRequest class to help with uploading file slices
13 | ///
14 | /// The type to be uploaded
15 | internal class UploadSliceRequest : BaseRequest
16 | {
17 | private UploadResponseHandler responseHandler;
18 |
19 | ///
20 | /// The beginning of the slice range to send.
21 | ///
22 | public long RangeBegin { get; private set; }
23 |
24 | ///
25 | /// The end of the slice range to send.
26 | ///
27 | public long RangeEnd { get; private set; }
28 |
29 | ///
30 | /// The length in bytes of the session.
31 | ///
32 | public long TotalSessionLength { get; private set; }
33 |
34 | ///
35 | /// The range length of the slice to send.
36 | ///
37 | public int RangeLength => (int)(this.RangeEnd - this.RangeBegin + 1);
38 |
39 | ///
40 | /// Request for uploading one slice of a session
41 | ///
42 | /// URL to upload the slice.
43 | /// Client used for sending the slice.
44 | /// Beginning of range of this slice
45 | /// End of range of this slice
46 | /// Total session length. This MUST be consistent
47 | /// across all slice.
48 | public UploadSliceRequest(
49 | string sessionUrl,
50 | IBaseClient client,
51 | long rangeBegin,
52 | long rangeEnd,
53 | long totalSessionLength)
54 | : base(sessionUrl, client, null)
55 | {
56 | this.RangeBegin = rangeBegin;
57 | this.RangeEnd = rangeEnd;
58 | this.TotalSessionLength = totalSessionLength;
59 | this.responseHandler = new UploadResponseHandler();
60 | }
61 |
62 | ///
63 | /// Uploads the slice using PUT.
64 | ///
65 | /// Stream of data to be sent in the request.
66 | /// The status of the upload.
67 | public Task> PutAsync(Stream stream)
68 | {
69 | return this.PutAsync(stream, CancellationToken.None);
70 | }
71 |
72 | ///
73 | /// Uploads the slice using PUT.
74 | ///
75 | /// Stream of data to be sent in the request. Length must be equal to the length
76 | /// of this slice (as defined by this.RangeLength)
77 | /// The cancellation token
78 | /// The status of the upload. If UploadSession.AdditionalData.ContainsKey("successResponse")
79 | /// is true, then the item has completed, and the value is the created item from the server.
80 | public virtual async Task> PutAsync(Stream stream, CancellationToken cancellationToken)
81 | {
82 | this.Method = HttpMethods.PUT;
83 | this.ContentType = CoreConstants.MimeTypeNames.Application.Stream;
84 | using (var response = await this.SendRequestAsync(stream, cancellationToken).ConfigureAwait(false))
85 | {
86 | return await this.responseHandler.HandleResponse(response).ConfigureAwait(false);
87 | }
88 | }
89 |
90 | ///
91 | /// Send the Sliced Upload request
92 | ///
93 | /// Stream of data to be sent in the request.
94 | /// The cancellation token
95 | /// The completion option for the request. Defaults to ResponseContentRead.
96 | ///
97 | private async Task SendRequestAsync(
98 | Stream stream,
99 | CancellationToken cancellationToken,
100 | HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
101 | {
102 | // Append the relevant headers for the range upload request
103 | using (var request = this.GetHttpRequestMessage())
104 | {
105 | request.Content = new StreamContent(stream);
106 | request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
107 | request.Content.Headers.ContentRange = new ContentRangeHeaderValue(this.RangeBegin, this.RangeEnd, this.TotalSessionLength);
108 | request.Content.Headers.ContentLength = this.RangeLength;
109 |
110 | return await this.Client.HttpProvider.SendAsync(request, completionOption, cancellationToken).ConfigureAwait(false);
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/AssignmentSubmission.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class AssignmentSubmission
11 | {
12 | [JsonProperty("anonymous_id")]
13 | public string AnonymousId { get; set; }
14 |
15 | [JsonProperty("assignment")]
16 | public object Assignment { get; set; }
17 |
18 | [JsonProperty("assignment_id")]
19 | public long AssignmentId { get; set; }
20 |
21 | [JsonProperty("assignment_visible")]
22 | public bool AssignmentVisible { get; set; }
23 |
24 | [JsonProperty("attempt")]
25 | public long? Attempt { get; set; }
26 |
27 | [JsonProperty("body")]
28 | public string Body { get; set; }
29 |
30 | [JsonProperty("course")]
31 | public object Course { get; set; }
32 |
33 | [JsonProperty("excused")]
34 | public bool? Excused { get; set; }
35 |
36 | [JsonProperty("extra_attempts")]
37 | public long? ExtraAttempts { get; set; }
38 |
39 | [JsonProperty("grade")]
40 | public string Grade { get; set; }
41 |
42 | [JsonProperty("grade_matches_current_submission")]
43 | public bool GradeMatchesCurrentSubmission { get; set; }
44 |
45 | [JsonProperty("graded_at")]
46 | public DateTime? GradedAt { get; set; }
47 |
48 | [JsonProperty("grader_id")]
49 | public long? GraderId { get; set; }
50 |
51 | [JsonProperty("html_url")]
52 | public string HtmlUrl { get; set; }
53 |
54 | [JsonProperty("late")]
55 | public bool Late { get; set; }
56 |
57 | [JsonProperty("late_policy_status")]
58 | public string LatePolicyStatus { get; set; }
59 |
60 | [JsonProperty("missing")]
61 | public bool Missing { get; set; }
62 |
63 | [JsonProperty("points_deducted")]
64 | public double? PointsDeducted { get; set; }
65 |
66 | [JsonProperty("posted_at")]
67 | public string PostedAt { get; set; }
68 |
69 | [JsonProperty("preview_url")]
70 | public string PreviewUrl { get; set; }
71 |
72 | [JsonProperty("read_status")]
73 | public string ReadStatus { get; set; }
74 |
75 | [JsonProperty("redo_request")]
76 | public bool RedoRequest { get; set; }
77 |
78 | [JsonProperty("score")]
79 | public double? Score { get; set; }
80 |
81 | [JsonProperty("seconds_late")]
82 | public long SecondsLate { get; set; }
83 |
84 | [JsonProperty("submission_comments")]
85 | public List SubmissionComments { get; set; }
86 |
87 | [JsonProperty("submission_type")]
88 | public string SubmissionType { get; set; }
89 |
90 | [JsonProperty("submitted_at")]
91 | public DateTime? SubmittedAt { get; set; }
92 |
93 | [JsonProperty("url")]
94 | public object Url { get; set; }
95 |
96 | [JsonProperty("user")]
97 | public object User { get; set; }
98 |
99 | [JsonProperty("user_id")]
100 | public long UserId { get; set; }
101 |
102 | [JsonProperty("workflow_state")]
103 | public string WorkflowState { get; set; }
104 |
105 | [JsonProperty("attachments")]
106 | public List Attachments { get; set; }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/CanvasLoginResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Models.CanvasModels
8 | {
9 | public class CanvasLoginResult : CommonResult
10 | {
11 | public UserProfile User { get; set; }
12 |
13 | public CanvasLoginResult(bool success, string result)
14 | {
15 | this.success = success;
16 | this.result = result;
17 | }
18 |
19 | public CanvasLoginResult(UserProfile userProfile)
20 | {
21 | this.User = userProfile;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/Common.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class LockInfo
11 | {
12 | [JsonProperty("asset_string")]
13 | public string? AssetString { get; set; }
14 |
15 | [JsonProperty("lock_at")]
16 | public string? LockAt { get; set; }
17 |
18 | [JsonProperty("manually_locked")]
19 | public bool? ManuallyLocked { get; set; }
20 |
21 | [JsonProperty("unlock_at")]
22 | public string? UnlockAt { get; set; }
23 | }
24 |
25 | public class Author
26 | {
27 | [JsonProperty("avatar_image_url")]
28 | public string AvatarImageUrl { get; set; }
29 |
30 | [JsonProperty("display_name")]
31 | public string DisplayName { get; set; }
32 |
33 | [JsonProperty("html_url")]
34 | public string HtmlUrl { get; set; }
35 |
36 | [JsonProperty("id")]
37 | public long? Id { get; set; }
38 | }
39 |
40 | public class Calendar
41 | {
42 | [JsonProperty("ics")]
43 | public string Ics { get; set; }
44 | }
45 |
46 | public class Attachment
47 | {
48 | [JsonProperty("content-type")]
49 | public string ContentType { get; set; }
50 |
51 | [JsonProperty("created_at")]
52 | public string CreatedAt { get; set; }
53 |
54 | [JsonProperty("display_name")]
55 | public string DisplayName { get; set; }
56 |
57 | [JsonProperty("filename")]
58 | public string Filename { get; set; }
59 |
60 | [JsonProperty("folder_id")]
61 | public long? FolderId { get; set; }
62 |
63 | [JsonProperty("hidden")]
64 | public bool Hidden { get; set; }
65 |
66 | [JsonProperty("hidden_for_user")]
67 | public bool HiddenForUser { get; set; }
68 |
69 | [JsonProperty("id")]
70 | public long Id { get; set; }
71 |
72 | [JsonProperty("lock_at")]
73 | public object LockAt { get; set; }
74 |
75 | [JsonProperty("locked")]
76 | public bool Locked { get; set; }
77 |
78 | [JsonProperty("locked_for_user")]
79 | public bool LockedForUser { get; set; }
80 |
81 | [JsonProperty("media_entry_id")]
82 | public object MediaEntryId { get; set; }
83 |
84 | [JsonProperty("mime_class")]
85 | public string MimeClass { get; set; }
86 |
87 | [JsonProperty("modified_at")]
88 | public string ModifiedAt { get; set; }
89 |
90 | [JsonProperty("size")]
91 | public long? Size { get; set; }
92 |
93 | [JsonProperty("thumbnail_url")]
94 | public object ThumbnailUrl { get; set; }
95 |
96 | [JsonProperty("unlock_at")]
97 | public object UnlockAt { get; set; }
98 |
99 | [JsonProperty("updated_at")]
100 | public string UpdatedAt { get; set; }
101 |
102 | [JsonProperty("url")]
103 | public string Url { get; set; }
104 |
105 | [JsonProperty("uuid")]
106 | public string Uuid { get; set; }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/Course.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class Course
11 | {
12 | [JsonProperty("account_id")]
13 | public long AccountId { get; set; }
14 |
15 | [JsonProperty("apply_assignment_group_weights")]
16 | public bool ApplyAssignmentGroupWeights { get; set; }
17 |
18 | [JsonProperty("blueprint")]
19 | public bool Blueprint { get; set; }
20 |
21 | [JsonProperty("calendar")]
22 | public Calendar Calendar { get; set; }
23 |
24 | [JsonProperty("course_code")]
25 | public string CourseCode { get; set; }
26 |
27 | [JsonProperty("default_view")]
28 | public string DefaultView { get; set; }
29 |
30 | [JsonProperty("end_at")]
31 | public string EndAt { get; set; }
32 |
33 | [JsonProperty("enrollment_term_id")]
34 | public long EnrollmentTermId { get; set; }
35 |
36 | [JsonProperty("enrollments")]
37 | public System.Collections.Generic.List Enrollments { get; set; }
38 |
39 | [JsonProperty("grading_standard_id")]
40 | public object GradingStandardId { get; set; }
41 |
42 | [JsonProperty("hide_final_grades")]
43 | public bool HideFinalGrades { get; set; }
44 |
45 | [JsonProperty("id")]
46 | public long Id { get; set; }
47 |
48 | [JsonProperty("is_public")]
49 | public bool? IsPublic { get; set; }
50 |
51 | [JsonProperty("is_public_to_auth_users")]
52 | public bool IsPublicToAuthUsers { get; set; }
53 |
54 | [JsonProperty("name")]
55 | public string Name { get; set; }
56 |
57 | [JsonProperty("original_name")]
58 | public string OriginalName { get; set; }
59 |
60 | [JsonProperty("public_syllabus")]
61 | public bool PublicSyllabus { get; set; }
62 |
63 | [JsonProperty("public_syllabus_to_auth")]
64 | public bool PublicSyllabusToAuth { get; set; }
65 |
66 | [JsonProperty("restrict_enrollments_to_course_dates")]
67 | public bool RestrictEnrollmentsToCourseDates { get; set; }
68 |
69 | [JsonProperty("root_account_id")]
70 | public long RootAccountId { get; set; }
71 |
72 | [JsonProperty("start_at")]
73 | public string StartAt { get; set; }
74 |
75 | [JsonProperty("storage_quota_mb")]
76 | public long StorageQuotaMb { get; set; }
77 |
78 | [JsonProperty("time_zone")]
79 | public string TimeZone { get; set; }
80 |
81 | [JsonProperty("uuid")]
82 | public string Uuid { get; set; }
83 |
84 | [JsonProperty("workflow_state")]
85 | public string WorkflowState { get; set; }
86 | }
87 |
88 | public class Enrollment
89 | {
90 | [JsonProperty("enrollment_state")]
91 | public string EnrollmentState { get; set; }
92 |
93 | [JsonProperty("role")]
94 | public string Role { get; set; }
95 |
96 | [JsonProperty("role_id")]
97 | public long RoleId { get; set; }
98 |
99 | [JsonProperty("type")]
100 | public string Type { get; set; }
101 |
102 | [JsonProperty("user_id")]
103 | public long UserId { get; set; }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/Discussion.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class Discussion : ICanvasItem
11 | {
12 | [JsonProperty("allow_rating")]
13 | public bool AllowRating { get; set; }
14 |
15 | [JsonProperty("assignment_id")]
16 | public object AssignmentId { get; set; }
17 |
18 | [JsonProperty("attachments")]
19 | public System.Collections.Generic.List Attachments { get; set; }
20 |
21 | [JsonProperty("author")]
22 | public Author Author { get; set; }
23 |
24 | [JsonProperty("delayed_post_at")]
25 | public DateTime? DelayedPostAt { get; set; }
26 |
27 | [JsonProperty("discussion_subentry_count")]
28 | public long DiscussionSubentryCount { get; set; }
29 |
30 | [JsonProperty("discussion_type")]
31 | public string DiscussionType { get; set; }
32 |
33 | [JsonProperty("group_category_id")]
34 | public object GroupCategoryId { get; set; }
35 |
36 | [JsonProperty("group_topic_children")]
37 | public System.Collections.Generic.List GroupTopicChildren { get; set; }
38 |
39 | [JsonProperty("html_url")]
40 | public string HtmlUrl { get; set; }
41 |
42 | [JsonProperty("id")]
43 | public long Id { get; set; }
44 |
45 | [JsonProperty("last_reply_at")]
46 | public DateTime? LastReplyAt { get; set; }
47 |
48 | [JsonProperty("lock_at")]
49 | public DateTime? LockAt { get; set; }
50 |
51 | [JsonProperty("lock_explanation")]
52 | public string LockExplanation { get; set; }
53 |
54 | [JsonProperty("lock_info")]
55 | public object LockInfo { get; set; }
56 |
57 | [JsonProperty("locked")]
58 | public bool Locked { get; set; }
59 |
60 | [JsonProperty("locked_for_user")]
61 | public bool LockedForUser { get; set; }
62 |
63 | [JsonProperty("message")]
64 | public string Message { get; set; }
65 |
66 | [JsonProperty("only_graders_can_rate")]
67 | public bool OnlyGradersCanRate { get; set; }
68 |
69 | [JsonProperty("permissions")]
70 | public DiscussionPermissions Permissions { get; set; }
71 |
72 | [JsonProperty("pinned")]
73 | public bool Pinned { get; set; }
74 |
75 | [JsonProperty("podcast_url")]
76 | public string PodcastUrl { get; set; }
77 |
78 | [JsonProperty("posted_at")]
79 | public DateTime? PostedAt { get; set; }
80 |
81 | [JsonProperty("published")]
82 | public bool Published { get; set; }
83 |
84 | [JsonProperty("read_state")]
85 | public string ReadState { get; set; }
86 |
87 | [JsonProperty("require_initial_post")]
88 | public bool? RequireInitialPost { get; set; }
89 |
90 | [JsonProperty("root_topic_id")]
91 | public object RootTopicId { get; set; }
92 |
93 | [JsonProperty("sort_by_rating")]
94 | public bool SortByRating { get; set; }
95 |
96 | [JsonProperty("subscribed")]
97 | public bool Subscribed { get; set; }
98 |
99 | [JsonProperty("subscription_hold")]
100 | public string SubscriptionHold { get; set; }
101 |
102 | [JsonProperty("title")]
103 | public string Title { get; set; }
104 |
105 | [JsonProperty("topic_children")]
106 | public System.Collections.Generic.List TopicChildren { get; set; }
107 |
108 | [JsonProperty("unread_count")]
109 | public long UnreadCount { get; set; }
110 |
111 | [JsonProperty("user_can_see_posts")]
112 | public bool UserCanSeePosts { get; set; }
113 |
114 | [JsonProperty("user_name")]
115 | public string UserName { get; set; }
116 | public string Content { get => $"{UserName}:\n{Message}"; set => throw new NotImplementedException(); }
117 | }
118 |
119 | public class GroupTopicChild
120 | {
121 | [JsonProperty("group_id")]
122 | public long GroupId { get; set; }
123 |
124 | [JsonProperty("id")]
125 | public long Id { get; set; }
126 | }
127 |
128 | public class DiscussionPermissions
129 | {
130 | [JsonProperty("attach")]
131 | public bool Attach { get; set; }
132 |
133 | [JsonProperty("delete")]
134 | public bool Delete { get; set; }
135 |
136 | [JsonProperty("reply")]
137 | public bool Reply { get; set; }
138 |
139 | [JsonProperty("update")]
140 | public bool Update { get; set; }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/ICanvasItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public interface ICanvasItem
11 | {
12 | long Id { get; set; }
13 | string Title { get; set; }
14 | string Content { get; set; }
15 | string HtmlUrl { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/Notification.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graph;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace TodoSynchronizer.Core.Models.CanvasModels
10 | {
11 | public class Notification : ICanvasItem
12 | {
13 | [JsonProperty("end_at")]
14 | public DateTime? EndAt { get; set; }
15 |
16 | [JsonProperty("icon")]
17 | public string Icon { get; set; }
18 |
19 | [JsonProperty("id")]
20 | public long Id { get; set; }
21 |
22 | [JsonProperty("message")]
23 | public string Message { get; set; }
24 |
25 | [JsonProperty("role_ids")]
26 | public long[] RoleIds { get; set; }
27 |
28 | [JsonProperty("roles")]
29 | public string[] Roles { get; set; }
30 |
31 | [JsonProperty("start_at")]
32 | public DateTime? StartAt { get; set; }
33 |
34 | [JsonProperty("subject")]
35 | public string Subject { get; set; }
36 |
37 | public string Title { get => Subject; set => throw new NotImplementedException(); }
38 | public string Content { get => Message; set => throw new NotImplementedException(); }
39 | public string HtmlUrl { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/QuizSubmission.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public partial class QuizSubmissionDto
11 | {
12 | [JsonProperty("quiz_submissions")]
13 | public System.Collections.Generic.List QuizSubmissions { get; set; }
14 | }
15 |
16 | public partial class QuizSubmission
17 | {
18 | [JsonProperty("attempt")]
19 | public long Attempt { get; set; }
20 |
21 | [JsonProperty("end_at")]
22 | public DateTime EndAt { get; set; }
23 |
24 | [JsonProperty("extra_attempts")]
25 | public long? ExtraAttempts { get; set; }
26 |
27 | [JsonProperty("extra_time")]
28 | public long? ExtraTime { get; set; }
29 |
30 | [JsonProperty("finished_at")]
31 | public DateTime? FinishedAt { get; set; }
32 |
33 | [JsonProperty("fudge_points")]
34 | public float? FudgePoints { get; set; }
35 |
36 | [JsonProperty("has_seen_results")]
37 | public bool? HasSeenResults { get; set; }
38 |
39 | [JsonProperty("id")]
40 | public long Id { get; set; }
41 |
42 | [JsonProperty("kept_score")]
43 | public float? KeptScore { get; set; }
44 |
45 | [JsonProperty("manually_unlocked")]
46 | public bool? ManuallyUnlocked { get; set; }
47 |
48 | [JsonProperty("overdue_and_needs_submission")]
49 | public bool? OverdueAndNeedsSubmission { get; set; }
50 |
51 | [JsonProperty("quiz_id")]
52 | public long QuizId { get; set; }
53 |
54 | [JsonProperty("quiz_points_possible")]
55 | public float? QuizPointsPossible { get; set; }
56 |
57 | [JsonProperty("score")]
58 | public float? Score { get; set; }
59 |
60 | [JsonProperty("score_before_regrade")]
61 | public float? ScoreBeforeRegrade { get; set; }
62 |
63 | [JsonProperty("started_at")]
64 | public DateTime? StartedAt { get; set; }
65 |
66 | [JsonProperty("submission_id")]
67 | public long SubmissionId { get; set; }
68 |
69 | [JsonProperty("time_spent")]
70 | public long? TimeSpent { get; set; }
71 |
72 | [JsonProperty("user_id")]
73 | public long UserId { get; set; }
74 |
75 | [JsonProperty("workflow_state")]
76 | public string WorkflowState { get; set; }
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/SubmissionComment.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class SubmissionComment
11 | {
12 | [JsonProperty("author")]
13 | public Author Author { get; set; }
14 |
15 | [JsonProperty("author_id")]
16 | public long? AuthorId { get; set; }
17 |
18 | [JsonProperty("author_name")]
19 | public string AuthorName { get; set; }
20 |
21 | [JsonProperty("avatar_path")]
22 | public string AvatarPath { get; set; }
23 |
24 | [JsonProperty("comment")]
25 | public string Comment { get; set; }
26 |
27 | [JsonProperty("created_at")]
28 | public DateTime? CreatedAt { get; set; }
29 |
30 | [JsonProperty("edited_at")]
31 | public DateTime? EditedAt { get; set; }
32 |
33 | [JsonProperty("id")]
34 | public long? Id { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CanvasModels/User.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.CanvasModels
9 | {
10 | public class UserProfile
11 | {
12 | [JsonProperty("avatar_url")]
13 | public string AvatarUrl { get; set; }
14 |
15 | [JsonProperty("bio")]
16 | public object Bio { get; set; }
17 |
18 | [JsonProperty("calendar")]
19 | public Calendar Calendar { get; set; }
20 |
21 | [JsonProperty("id")]
22 | public long Id { get; set; }
23 |
24 | [JsonProperty("integration_id")]
25 | public object IntegrationId { get; set; }
26 |
27 | [JsonProperty("locale")]
28 | public object Locale { get; set; }
29 |
30 | [JsonProperty("login_id")]
31 | public string LoginId { get; set; }
32 |
33 | [JsonProperty("lti_user_id")]
34 | public string LtiUserId { get; set; }
35 |
36 | [JsonProperty("name")]
37 | public string Name { get; set; }
38 |
39 | [JsonProperty("primary_email")]
40 | public string PrimaryEmail { get; set; }
41 |
42 | [JsonProperty("short_name")]
43 | public string ShortName { get; set; }
44 |
45 | [JsonProperty("sortable_name")]
46 | public string SortableName { get; set; }
47 |
48 | [JsonProperty("time_zone")]
49 | public string TimeZone { get; set; }
50 |
51 | [JsonProperty("title")]
52 | public object Title { get; set; }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/CommonResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Models
8 | {
9 | public class CommonResult
10 | {
11 | public bool success;
12 | public string result;
13 |
14 | public CommonResult() { }
15 | public CommonResult(bool success, string result)
16 | {
17 | this.success = success;
18 | this.result = result;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaBatchCheckDto.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public partial class DidaBatchCheckDto
11 | {
12 | [JsonProperty("checkPoint")]
13 | public long CheckPoint { get; set; }
14 |
15 | [JsonProperty("filters")]
16 | public object Filters { get; set; }
17 |
18 | [JsonProperty("inboxId")]
19 | public string InboxId { get; set; }
20 |
21 | [JsonProperty("projectGroups")]
22 | public System.Collections.Generic.List ProjectGroups { get; set; }
23 |
24 | [JsonProperty("projectProfiles")]
25 | public System.Collections.Generic.List ProjectProfiles { get; set; }
26 |
27 | [JsonProperty("syncTaskBean")]
28 | public SyncTaskBean SyncTaskBean { get; set; }
29 |
30 | [JsonProperty("tags")]
31 | public System.Collections.Generic.List Tags { get; set; }
32 | }
33 | public partial class SyncTaskBean
34 | {
35 | [JsonProperty("update")]
36 | public System.Collections.Generic.List Update { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaBatchDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Models.DidaModels
8 | {
9 | public class DidaBatchDto
10 | {
11 | public DidaBatchDto()
12 | {
13 | add = new List();
14 | update = new List();
15 | delete = new List();
16 | addAttachments = new List();
17 | updateAttachments = new List();
18 | deleteAttachments = new List();
19 | }
20 |
21 | public List add { get; set; }
22 | public List update { get; set; }
23 | public List delete { get; set; }
24 | public List addAttachments { get; set; }
25 | public List updateAttachments { get; set; }
26 | public List deleteAttachments { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaCheckItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public class DidaCheckItem
11 | {
12 | [JsonProperty("completedTime")]
13 | public DateTime? CompletedTime { get; set; }
14 |
15 | [JsonProperty("id")]
16 | public string Id { get; set; }
17 |
18 | [JsonProperty("isAllDay")]
19 | public bool IsAllDay { get; set; }
20 |
21 | [JsonProperty("snoozeReminderTime")]
22 | public DateTime? SnoozeReminderTime { get; set; }
23 |
24 | [JsonProperty("sortOrder")]
25 | public long SortOrder { get; set; }
26 |
27 | [JsonProperty("startDate")]
28 | public object StartDate { get; set; }
29 |
30 | [JsonProperty("status")]
31 | public long Status { get; set; }
32 |
33 | [JsonProperty("timeZone")]
34 | public string TimeZone { get; set; }
35 |
36 | [JsonProperty("title")]
37 | public string Title { get; set; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaTask.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public partial class DidaTask
11 | {
12 | [JsonProperty("columnId")]
13 | public string ColumnId { get; set; }
14 |
15 | [JsonProperty("completedTime")]
16 | public DateTime? CompletedTime { get; set; }
17 |
18 | [JsonProperty("commentCount")]
19 | public long? CommentCount { get; set; }
20 |
21 | [JsonProperty("completedUserId")]
22 | public long? CompletedUserId { get; set; }
23 |
24 | [JsonProperty("content")]
25 | public string Content { get; set; }
26 |
27 | [JsonProperty("createdTime")]
28 | public DateTime? CreatedTime { get; set; }
29 |
30 | [JsonProperty("creator")]
31 | public long? Creator { get; set; }
32 |
33 | [JsonProperty("deleted")]
34 | public long? Deleted { get; set; }
35 |
36 | [JsonProperty("desc")]
37 | public string Desc { get; set; }
38 |
39 | [JsonProperty("dueDate")]
40 | public DateTime? DueDate { get; set; }
41 |
42 | [JsonProperty("etag")]
43 | public string Etag { get; set; }
44 |
45 | [JsonProperty("exDate")]
46 | public System.Collections.Generic.List ExDate { get; set; }
47 |
48 | [JsonProperty("id")]
49 | public string Id { get; set; }
50 |
51 | [JsonProperty("isAllDay")]
52 | public bool? IsAllDay { get; set; }
53 |
54 | [JsonProperty("isFloating")]
55 | public bool? IsFloating { get; set; }
56 |
57 | [JsonProperty("items")]
58 | public System.Collections.Generic.List Items { get; set; }
59 |
60 | [JsonProperty("kind")]
61 | public string Kind { get; set; }
62 |
63 | [JsonProperty("modifiedTime")]
64 | public DateTime? ModifiedTime { get; set; }
65 |
66 | [JsonProperty("priority")]
67 | public long? Priority { get; set; }
68 |
69 | [JsonProperty("progress")]
70 | public long? Progress { get; set; }
71 |
72 | [JsonProperty("projectId")]
73 | public string ProjectId { get; set; }
74 |
75 | [JsonProperty("repeatFirstDate")]
76 | public DateTime? RepeatFirstDate { get; set; }
77 |
78 | [JsonProperty("reminder")]
79 | public string Reminder { get; set; }
80 |
81 | [JsonProperty("reminders")]
82 | public System.Collections.Generic.List Reminders { get; set; }
83 |
84 | [JsonProperty("sortOrder")]
85 | public long? SortOrder { get; set; }
86 |
87 | [JsonProperty("startDate")]
88 | public DateTime? StartDate { get; set; }
89 |
90 | [JsonProperty("status")]
91 | public long? Status { get; set; }
92 |
93 | [JsonProperty("timeZone")]
94 | public string TimeZone { get; set; }
95 |
96 | [JsonProperty("title")]
97 | public string Title { get; set; }
98 | }
99 |
100 |
101 | public class Reminder
102 | {
103 | [JsonProperty("id")]
104 | public string Id { get; set; }
105 |
106 | [JsonProperty("trigger")]
107 | public string Trigger { get; set; }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaTaskComment.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public partial class DidaTaskComment
11 | {
12 | [JsonProperty("createdTime")]
13 | public DateTime? CreatedTime { get; set; }
14 |
15 | [JsonProperty("id")]
16 | public string Id { get; set; }
17 |
18 | [JsonProperty("isNew")]
19 | public bool? IsNew { get; set; }
20 |
21 | [JsonProperty("projectId")]
22 | public string ProjectId { get; set; }
23 |
24 | [JsonProperty("taskId")]
25 | public string TaskId { get; set; }
26 |
27 | [JsonProperty("title")]
28 | public string Title { get; set; }
29 |
30 | [JsonProperty("userProfile")]
31 | public UserProfile UserProfile { get; set; }
32 | }
33 |
34 | public partial class UserProfile
35 | {
36 | [JsonProperty("isMyself")]
37 | public bool IsMyself { get; set; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/DidaTaskList.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public partial class DidaTaskList
11 | {
12 | [JsonProperty("closed")]
13 | public object Closed { get; set; }
14 |
15 | [JsonProperty("color")]
16 | public object Color { get; set; }
17 |
18 | [JsonProperty("etag")]
19 | public string Etag { get; set; }
20 |
21 | [JsonProperty("groupId")]
22 | public object GroupId { get; set; }
23 |
24 | [JsonProperty("id")]
25 | public string Id { get; set; }
26 |
27 | [JsonProperty("inAll")]
28 | public bool? InAll { get; set; }
29 |
30 | [JsonProperty("isOwner")]
31 | public bool? IsOwner { get; set; }
32 |
33 | [JsonProperty("kind")]
34 | public string Kind { get; set; }
35 |
36 | [JsonProperty("modifiedTime")]
37 | public string ModifiedTime { get; set; }
38 |
39 | [JsonProperty("muted")]
40 | public bool? Muted { get; set; }
41 |
42 | [JsonProperty("name")]
43 | public string Name { get; set; }
44 |
45 | [JsonProperty("notificationOptions")]
46 | public object NotificationOptions { get; set; }
47 |
48 | [JsonProperty("permission")]
49 | public object Permission { get; set; }
50 |
51 | [JsonProperty("sortOrder")]
52 | public long? SortOrder { get; set; }
53 |
54 | [JsonProperty("sortType")]
55 | public string SortType { get; set; }
56 |
57 | [JsonProperty("teamId")]
58 | public object TeamId { get; set; }
59 |
60 | [JsonProperty("timeline")]
61 | public object Timeline { get; set; }
62 |
63 | [JsonProperty("transferred")]
64 | public object Transferred { get; set; }
65 |
66 | [JsonProperty("userCount")]
67 | public long? UserCount { get; set; }
68 |
69 | [JsonProperty("viewMode")]
70 | public string ViewMode { get; set; }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/DidaModels/LoginDto.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace TodoSynchronizer.Core.Models.DidaModels
9 | {
10 | public partial class LoginDto
11 | {
12 | [JsonProperty("activeTeamUser")]
13 | public bool ActiveTeamUser { get; set; }
14 |
15 | [JsonProperty("ds")]
16 | public bool Ds { get; set; }
17 |
18 | [JsonProperty("freeTrial")]
19 | public bool FreeTrial { get; set; }
20 |
21 | [JsonProperty("inboxId")]
22 | public string InboxId { get; set; }
23 |
24 | [JsonProperty("needSubscribe")]
25 | public bool NeedSubscribe { get; set; }
26 |
27 | [JsonProperty("phone")]
28 | public string Phone { get; set; }
29 |
30 | [JsonProperty("pro")]
31 | public bool Pro { get; set; }
32 |
33 | [JsonProperty("proEndDate")]
34 | public string ProEndDate { get; set; }
35 |
36 | [JsonProperty("teamUser")]
37 | public bool TeamUser { get; set; }
38 |
39 | [JsonProperty("token")]
40 | public string Token { get; set; }
41 |
42 | [JsonProperty("userId")]
43 | public string UserId { get; set; }
44 |
45 | [JsonProperty("username")]
46 | public string Username { get; set; }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/MyReadOnlySubStream.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3 | // ---------------
4 |
5 | namespace Microsoft.Graph
6 | {
7 | using System;
8 | using System.Diagnostics;
9 | using System.IO;
10 |
11 | ///
12 | /// Helper stream class to represent a slice of a larger stream to save memory when dealing with large streams
13 | /// and remove the extra copy operations
14 | /// This class is inspired from System.IO.Compression in dot net core. Reference implementation can be found here
15 | /// https://github.com/dotnet/corefx/blob/d59f6e5a1bdabdd05317fd727efb59345e328b80/src/System.IO.Compression/src/System/IO/Compression/ZipCustomStreams.cs#L147
16 | ///
17 | internal class MyReadOnlySubStream : Stream
18 | {
19 | private readonly long _startInSuperStream;
20 | private long _positionInSuperStream;
21 | private readonly long _endInSuperStream;
22 | private readonly Stream _superStream;
23 | private bool _canRead;
24 | private bool _isDisposed;
25 |
26 | public MyReadOnlySubStream(Stream superStream, long startPosition, long maxLength)
27 | {
28 | this._startInSuperStream = startPosition;
29 | this._positionInSuperStream = startPosition;
30 | this._endInSuperStream = startPosition + maxLength;
31 | this._superStream = superStream;
32 | this._canRead = true;
33 | this._isDisposed = false;
34 | }
35 |
36 | public override long Length
37 | {
38 | get
39 | {
40 | ThrowIfDisposed();
41 |
42 | return _endInSuperStream - _startInSuperStream;
43 | }
44 | }
45 |
46 | public override long Position
47 | {
48 | get
49 | {
50 | ThrowIfDisposed();
51 |
52 | return _positionInSuperStream - _startInSuperStream;
53 | }
54 | set
55 | {
56 | ThrowIfDisposed();
57 |
58 | throw new NotSupportedException("seek not support");
59 | }
60 | }
61 |
62 | public override bool CanRead => _superStream.CanRead && _canRead;
63 |
64 | public override bool CanSeek => false;
65 |
66 | public override bool CanWrite => false;
67 |
68 | private void ThrowIfDisposed()
69 | {
70 | if (_isDisposed)
71 | throw new ObjectDisposedException(GetType().ToString(), nameof(this._superStream));
72 | }
73 |
74 | private void ThrowIfCantRead()
75 | {
76 | if (!CanRead)
77 | throw new NotSupportedException("read not support");
78 | }
79 |
80 | public override int Read(byte[] buffer, int offset, int count)
81 | {
82 | // parameter validation sent to _superStream.Read
83 | int origCount = count;
84 |
85 | ThrowIfDisposed();
86 | ThrowIfCantRead();
87 |
88 | if (_superStream.Position != _positionInSuperStream)
89 | _superStream.Seek(_positionInSuperStream, SeekOrigin.Begin);
90 | if (_positionInSuperStream + count > _endInSuperStream)
91 | count = (int)(_endInSuperStream - _positionInSuperStream);
92 |
93 | Debug.Assert(count >= 0);
94 | Debug.Assert(count <= origCount);
95 |
96 | int ret = _superStream.Read(buffer, offset, count);
97 |
98 | _positionInSuperStream += ret;
99 | return ret;
100 | }
101 |
102 | public override long Seek(long offset, SeekOrigin origin)
103 | {
104 | ThrowIfDisposed();
105 | throw new NotSupportedException("seek not support");
106 | }
107 |
108 | public override void SetLength(long value)
109 | {
110 | ThrowIfDisposed();
111 | throw new NotSupportedException("seek and write not support");
112 | }
113 |
114 | public override void Write(byte[] buffer, int offset, int count)
115 | {
116 | ThrowIfDisposed();
117 | throw new NotSupportedException("write not support");
118 | }
119 |
120 | public override void Flush()
121 | {
122 | ThrowIfDisposed();
123 | throw new NotSupportedException("write not support");
124 | }
125 |
126 | // Close the stream for reading. Note that this does NOT close the superStream (since
127 | // the subStream is just 'a chunk' of the super-stream
128 | protected override void Dispose(bool disposing)
129 | {
130 | if (disposing && !_isDisposed)
131 | {
132 | _canRead = false;
133 | _isDisposed = true;
134 | }
135 | base.Dispose(disposing);
136 | }
137 | }
138 | }
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/SyncState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.Core.Models
8 | {
9 | public class SyncState
10 | {
11 | public SyncState(SyncStateEnum state, string message)
12 | {
13 | State = state;
14 | Message = message;
15 | }
16 |
17 | public SyncStateEnum State { get; set; }
18 | public string Message { get; set; }
19 | }
20 |
21 | public enum SyncStateEnum
22 | {
23 | Finished, Error, Progress
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Models/WebResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http.Headers;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace TodoSynchronizer.Core.Models
10 | {
11 | public class WebResult : CommonResult
12 | {
13 | public HttpStatusCode? code;
14 | public string message;
15 | public WebHeaderCollection headers;
16 | public HttpResponseHeaders headers1;
17 |
18 | public WebResult(HttpStatusCode? code, bool success, string result, string message)
19 | {
20 | this.code = code;
21 | this.success = success;
22 | this.result = result;
23 | this.message = message;
24 | }
25 |
26 | public WebResult(HttpStatusCode? code, bool success, string result, string message, HttpResponseHeaders headers)
27 | {
28 | this.code = code;
29 | this.success = success;
30 | this.result = result;
31 | this.message = message;
32 | this.headers1 = headers;
33 | }
34 |
35 | public WebResult(HttpStatusCode? code, bool success, string result, string message, WebHeaderCollection headers)
36 | {
37 | this.code = code;
38 | this.success = success;
39 | this.result = result;
40 | this.message = message;
41 | this.headers = headers;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Services/Web.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Text;
8 | using TodoSynchronizer.Core.Extensions;
9 | using TodoSynchronizer.Core.Models;
10 |
11 | namespace TodoSynchronizer.Core.Service
12 | {
13 | public static class Web
14 | {
15 | public static WebResult Get(HttpClient client, string url, Dictionary queryparas)
16 | {
17 | return Get(client, BuildUrl(url, queryparas));
18 | }
19 |
20 | public static WebResult Get(HttpClient client, string url, Dictionary queryparas, Dictionary headers)
21 | {
22 | ProcessHeaders(client, headers);
23 | var task = client.GetAsync(url);
24 | task.Wait();
25 | return GetFinalResult(task.GetAwaiter().GetResult());
26 | }
27 |
28 | public static WebResult Get(HttpClient client, string url)
29 | {
30 | var task = client.GetAsync(url);
31 | task.Wait();
32 | return GetFinalResult(task.GetAwaiter().GetResult());
33 | }
34 | public static WebResult Post(HttpClient client, string url, string content)
35 | {
36 | var httpcontent = new StringContent(content);
37 | httpcontent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
38 | var task = client.PostAsync(url, httpcontent);
39 | task.Wait();
40 | return GetFinalResult(task.GetAwaiter().GetResult());
41 | }
42 |
43 | public static void ProcessHeaders(HttpClient client, Dictionary headers)
44 | {
45 | //if (headers.ContainsKey("Accept"))
46 | //{
47 | // client.DefaultRequestHeaders.Add = headers["Accept"];
48 | // headers.Remove("Accept");
49 | //}
50 | //if (headers.ContainsKey("User-Agent"))
51 | //{
52 | // req.UserAgent = headers["User-Agent"];
53 | // headers.Remove("User-Agent");
54 | //}
55 | //if (headers.ContainsKey("Referer"))
56 | //{
57 | // req.Referer = headers["Referer"];
58 | // headers.Remove("Referer");
59 | //}
60 | //if (headers.ContainsKey("Connection") && headers["Connection"] == "keep-alive")
61 | //{
62 | // req.KeepAlive = true;
63 | // headers.Remove("Connection");
64 | //}
65 | //if (headers.ContainsKey("Content-Type"))
66 | //{
67 | // req.ContentType = headers["Content-Type"];
68 | // headers.Remove("Content-Type");
69 | //}
70 | if (headers.ContainsKey("Host"))
71 | {
72 | headers.Remove("Host");
73 | }
74 | if (headers.ContainsKey("Content-Length"))
75 | {
76 | headers.Remove("Content-Length");
77 | }
78 | if (headers.ContainsKey("Cache-Control"))
79 | {
80 | headers.Remove("Cache-Control");
81 | }
82 | foreach (var i in headers)
83 | {
84 | client.DefaultRequestHeaders.Add(i.Key, i.Value);
85 | }
86 | }
87 |
88 | public static WebResult GetFinalResult(HttpResponseMessage responseMessage)
89 | {
90 | return new WebResult(responseMessage.StatusCode, true, responseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult(), null, responseMessage.Headers);
91 | }
92 |
93 | public static string BuildUrl(string url, Dictionary queryparas)
94 | {
95 | StringBuilder builder1 = new StringBuilder();
96 | builder1.Append(url);
97 |
98 | if (queryparas.Count > 0)
99 | {
100 | builder1.Append("?");
101 | int i = 0;
102 | foreach (var item in queryparas)
103 | {
104 | if (i > 0)
105 | builder1.Append("&");
106 | builder1.AppendFormat("{0}={1}", item.Key, item.Value);
107 | i++;
108 | }
109 | }
110 | return builder1.ToString();
111 | }
112 |
113 | public static string BuildForm(Dictionary formdata, bool urlencode)
114 | {
115 | StringBuilder builder = new StringBuilder();
116 |
117 | if (formdata.Count > 0)
118 | {
119 | int i = 0;
120 | foreach (var item in formdata)
121 | {
122 | if (i > 0)
123 | builder.Append("&");
124 | builder.AppendFormat("{0}={1}", item.Key, urlencode ? item.Value.UrlUnescape() : item.Value);
125 | i++;
126 | }
127 | }
128 | return builder.ToString();
129 | }
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/TodoSynchronizer.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | net6.0
6 | disable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/TodoSynchronizer.Core/Yaml/IgnoreCaseTypeInspector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using YamlDotNet.Serialization;
8 |
9 | namespace TodoSynchronizer.Core.Yaml
10 | {
11 | public class IgnoreCaseTypeInspector : ITypeInspector
12 | {
13 | private readonly ITypeInspector innerTypeInspector;
14 |
15 | public IgnoreCaseTypeInspector(ITypeInspector innerTypeInspector)
16 | {
17 | this.innerTypeInspector = innerTypeInspector ?? throw new ArgumentNullException(nameof(innerTypeInspector));
18 | }
19 |
20 | public IEnumerable GetProperties(Type type, object? container)
21 | {
22 | return innerTypeInspector.GetProperties(type, container);
23 | }
24 |
25 | public IPropertyDescriptor GetProperty(Type type, object? container, string name, bool ignoreUnmatched)
26 | {
27 | var candidates = GetProperties(type, container)
28 | .Where(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
29 |
30 | using (var enumerator = candidates.GetEnumerator())
31 | {
32 | if (!enumerator.MoveNext())
33 | {
34 | if (ignoreUnmatched)
35 | {
36 | return null!;
37 | }
38 |
39 | throw new SerializationException($"Property '{name}' not found on type '{type.FullName}'.");
40 | }
41 |
42 | var property = enumerator.Current;
43 |
44 | if (enumerator.MoveNext())
45 | {
46 | throw new SerializationException(
47 | $"Multiple properties with the name/alias '{name}' already exists on type '{type.FullName}', maybe you're misusing YamlAlias or maybe you are using the wrong naming convention? The matching properties are: {string.Join(", ", candidates.Select(p => p.Name).ToArray())}"
48 | );
49 | }
50 |
51 | return property;
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TodoSynchronizer.QuickTool
10 | {
11 | ///
12 | /// App.xaml 的交互逻辑
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Broker/IUriInterceptor.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser
9 | {
10 | ///
11 | /// An abstraction over objects that are able to listen to localhost url (e.g. http://localhost:1234)
12 | /// and to retrieve the whole url, including query params (e.g. http://localhost:1234?code=auth_code_from_aad)
13 | ///
14 | internal interface IUriInterceptor
15 | {
16 | ///
17 | /// Listens to http://localhost:{port} and retrieve the entire url, including query params. Then
18 | /// push back a response such as a display message or a redirect.
19 | ///
20 | /// Cancellation is very important as this is typically a long running unmonitored operation
21 | /// the port to listen to
22 | /// the path to listen in
23 | /// The message to be displayed, or url to be redirected to will be created by this callback
24 | /// Cancellation token
25 | /// Full redirect uri
26 | Task ListenToSingleRequestAndRespondAsync(
27 | int port,
28 | string path,
29 | Func responseProducer,
30 | CancellationToken cancellationToken);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Broker/MessageAndHttpCode.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Net;
6 |
7 | namespace Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser
8 | {
9 | internal class MessageAndHttpCode
10 | {
11 | public MessageAndHttpCode(HttpStatusCode httpCode, string message)
12 | {
13 | HttpCode = httpCode;
14 | Message = message ?? throw new ArgumentNullException(nameof(message));
15 | }
16 |
17 | public HttpStatusCode HttpCode { get; }
18 | public string Message { get; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Helpers/HardwareAcceleration.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the MIT License.
2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors.
4 | // All Rights Reserved.
5 |
6 | using System.Windows.Media;
7 |
8 | namespace Wpf.Ui.Hardware;
9 |
10 | ///
11 | /// Set of tools for hardware acceleration.
12 | ///
13 | public static class HardwareAcceleration
14 | {
15 | ///
16 | /// Determines whether the provided rendering tier is supported.
17 | ///
18 | /// Hardware acceleration rendering tier to check.
19 | /// if tier is supported.
20 | public static bool IsSupported(RenderingTier tier)
21 | {
22 | return RenderCapability.Tier >> 16 >= (int)tier;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Helpers/WordHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.QuickTool.Helpers
8 | {
9 | using System;
10 | using System.Text;
11 |
12 | public static class WordHelper
13 | {
14 | public static string GetRandomChinese(int strlength)
15 | {
16 | // 获取GB2312编码页(表)
17 | Encoding gb = Encoding.GetEncoding("gb2312");
18 |
19 | object[] bytes = CreateRegionCode(strlength);
20 |
21 | StringBuilder sb = new StringBuilder();
22 |
23 | for (int i = 0; i < strlength; i++)
24 | {
25 | string temp = gb.GetString((byte[])Convert.ChangeType(bytes[i], typeof(byte[])));
26 | sb.Append(temp);
27 | }
28 |
29 | return sb.ToString();
30 | }
31 |
32 | /**
33 | 此函数在汉字编码范围内随机创建含两个元素的十六进制字节数组,每个字节数组代表一个汉字,并将
34 | 四个字节数组存储在object数组中。
35 | 参数:strlength,代表需要产生的汉字个数
36 | **/
37 | private static object[] CreateRegionCode(int strlength)
38 | {
39 | //定义一个字符串数组储存汉字编码的组成元素
40 | string[] rBase = new String[16] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
41 |
42 | Random rnd = new Random();
43 |
44 | //定义一个object数组用来
45 | object[] bytes = new object[strlength];
46 |
47 | /**
48 | 每循环一次产生一个含两个元素的十六进制字节数组,并将其放入bytes数组中
49 | 每个汉字有四个区位码组成
50 | 区位码第1位和区位码第2位作为字节数组第一个元素
51 | 区位码第3位和区位码第4位作为字节数组第二个元素
52 | **/
53 | for (int i = 0; i < strlength; i++)
54 | {
55 | //区位码第1位
56 | int r1 = rnd.Next(11, 14);
57 | string str_r1 = rBase[r1].Trim();
58 |
59 | //区位码第2位
60 | rnd = new Random(r1 * unchecked((int)DateTime.Now.Ticks) + i); // 更换随机数发生器的 种子避免产生重复值
61 | int r2;
62 | if (r1 == 13)
63 | {
64 | r2 = rnd.Next(0, 7);
65 | }
66 | else
67 | {
68 | r2 = rnd.Next(0, 16);
69 | }
70 | string str_r2 = rBase[r2].Trim();
71 |
72 | //区位码第3位
73 | rnd = new Random(r2 * unchecked((int)DateTime.Now.Ticks) + i);
74 | int r3 = rnd.Next(10, 16);
75 | string str_r3 = rBase[r3].Trim();
76 |
77 | //区位码第4位
78 | rnd = new Random(r3 * unchecked((int)DateTime.Now.Ticks) + i);
79 | int r4;
80 | if (r3 == 10)
81 | {
82 | r4 = rnd.Next(1, 16);
83 | }
84 | else if (r3 == 15)
85 | {
86 | r4 = rnd.Next(0, 15);
87 | }
88 | else
89 | {
90 | r4 = rnd.Next(0, 16);
91 | }
92 | string str_r4 = rBase[r4].Trim();
93 |
94 | // 定义两个字节变量存储产生的随机汉字区位码
95 | byte byte1 = Convert.ToByte(str_r1 + str_r2, 16);
96 | byte byte2 = Convert.ToByte(str_r3 + str_r4, 16);
97 | // 将两个字节变量存储在字节数组中
98 | byte[] str_r = new byte[] { byte1, byte2 };
99 |
100 | // 将产生的一个汉字的字节数组放入object数组中
101 | bytes.SetValue(str_r, i);
102 | }
103 |
104 | return bytes;
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 | using TodoSynchronizer.QuickTool.Pages;
18 | using TodoSynchronizer.QuickTool.Services;
19 |
20 | namespace TodoSynchronizer.QuickTool
21 | {
22 | ///
23 | /// MainWindow.xaml 的交互逻辑
24 | ///
25 | public partial class MainWindow : Window
26 | {
27 | public MainWindow()
28 | {
29 | InitializeComponent();
30 | NaviService.SetFrame(RootFrame);
31 | }
32 |
33 |
34 | private void Window_Loaded(object sender, RoutedEventArgs e)
35 | {
36 | NaviService.Navigate(new Page1());
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page1.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page1.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 | using TodoSynchronizer.QuickTool.Services;
18 |
19 | namespace TodoSynchronizer.QuickTool.Pages
20 | {
21 | ///
22 | /// Page1.xaml 的交互逻辑
23 | ///
24 | public partial class Page1 : Page
25 | {
26 | public Page1()
27 | {
28 | InitializeComponent();
29 | }
30 |
31 | private async void Button_Click(object sender, RoutedEventArgs e)
32 | {
33 | Button1.IsEnabled = false;
34 | DefaultOsBrowserWebUi webUi = new DefaultOsBrowserWebUi();
35 | var res = await webUi.AcquireAuthorizationAsync(new Uri("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=49694ef2-8751-4ac9-8431-8817c27350b4&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A65399&response_mode=query&scope=Tasks.ReadWrite%20User.Read%20offline_access&state=12345"), new Uri("http://localhost:65399"), new System.Threading.CancellationToken());
36 | Debug.WriteLine(res);
37 | DataService.SetData("uri", res);
38 | NaviService.Navigate(new Page2());
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page2.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page2.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Security.Policy;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Threading.Tasks;
10 | using System.Windows;
11 | using System.Windows.Controls;
12 | using System.Windows.Data;
13 | using System.Windows.Documents;
14 | using System.Windows.Input;
15 | using System.Windows.Media;
16 | using System.Windows.Media.Imaging;
17 | using System.Windows.Navigation;
18 | using System.Windows.Shapes;
19 | using TodoSynchronizer.QuickTool.Services;
20 |
21 | namespace TodoSynchronizer.QuickTool.Pages
22 | {
23 | ///
24 | /// Page2.xaml 的交互逻辑
25 | ///
26 | public partial class Page2 : Page, INotifyPropertyChanged
27 | {
28 | public Page2()
29 | {
30 | InitializeComponent();
31 | this.DataContext = this;
32 | }
33 |
34 | private string head;
35 |
36 | public string Head
37 | {
38 | get { return head; }
39 | set
40 | {
41 | head = value;
42 | this.RaisePropertyChanged("Head");
43 | }
44 | }
45 |
46 |
47 | private string message;
48 |
49 | public string Message
50 | {
51 | get { return message; }
52 | set
53 | {
54 | message = value;
55 | this.RaisePropertyChanged("Message");
56 | }
57 | }
58 |
59 |
60 | private void Page_Loaded(object sender, RoutedEventArgs e)
61 | {
62 | var res = DataService.GetData("uri");
63 | var reg = new Regex(@"code=([a-zA-Z0-9-\._]+)");
64 | Match match = reg.Match(res);
65 | if (match.Success)
66 | {
67 | Head = "用户已授权";
68 | Message = "正在联系服务器获取信息...";
69 | Task.Run(() => {
70 | var forms = new List>();
71 | forms.Add(new KeyValuePair("client_id", "49694ef2-8751-4ac9-8431-8817c27350b4"));
72 | forms.Add(new KeyValuePair("scope", "Tasks.ReadWrite User.Read offline_access"));
73 | forms.Add(new KeyValuePair("redirect_uri", "http://localhost:65399"));
74 | forms.Add(new KeyValuePair("grant_type", "authorization_code"));
75 | forms.Add(new KeyValuePair("code", match.Groups[1].Value));
76 |
77 | FormUrlEncodedContent form = new FormUrlEncodedContent(forms);
78 |
79 | HttpClient client = new HttpClient();
80 | var posttask = client.PostAsync("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", form);
81 | posttask.Wait();
82 | var tokenres = posttask.GetAwaiter().GetResult();
83 |
84 | if (!tokenres.IsSuccessStatusCode)
85 | {
86 | Head = "获取信息失败";
87 | Message = tokenres.Content.ReadAsStringAsync().GetAwaiter().GetResult();
88 | }
89 | else
90 | {
91 | var reg1 = new Regex(@"""refresh_token"":""(.+?)""");
92 | var match1 = reg1.Match(tokenres.Content.ReadAsStringAsync().GetAwaiter().GetResult());
93 | if (match1.Success)
94 | {
95 | DataService.SetData("token", match1.Groups[1].Value);
96 | this.Dispatcher.Invoke(() => { NaviService.Navigate(new Page3()); });
97 | }
98 | else
99 | {
100 | Head = "获取信息失败";
101 | Message = tokenres.Content.ReadAsStringAsync().GetAwaiter().GetResult();
102 | }
103 | }
104 | });
105 | }
106 | else
107 | Head = "授权失败";
108 | Message = "错误代码:";
109 | }
110 |
111 | #region INotifyPropertyChanged members
112 |
113 | public event PropertyChangedEventHandler PropertyChanged;
114 |
115 | protected void RaisePropertyChanged(string propertyName)
116 | {
117 | PropertyChangedEventHandler handler = this.PropertyChanged;
118 | if (handler != null)
119 | {
120 | var e = new PropertyChangedEventArgs(propertyName);
121 | handler(this, e);
122 | }
123 | }
124 |
125 | #endregion
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page3.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page3.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using TodoSynchronizer.QuickTool.Helpers;
17 | using TodoSynchronizer.QuickTool.Services;
18 |
19 | namespace TodoSynchronizer.QuickTool.Pages
20 | {
21 | ///
22 | /// Page3.xaml 的交互逻辑
23 | ///
24 | public partial class Page3 : Page, INotifyPropertyChanged
25 | {
26 | public Page3()
27 | {
28 | InitializeComponent();
29 | this.DataContext = this;
30 | Password = WordHelper.GetRandomChinese(10);
31 | }
32 |
33 | private string password;
34 |
35 | public string Password
36 | {
37 | get { return password; }
38 | set
39 | {
40 | password = value;
41 | this.RaisePropertyChanged("Password");
42 | }
43 | }
44 |
45 |
46 | private void Button_Click(object sender, RoutedEventArgs e)
47 | {
48 | if (Password == string.Empty || Password == "")
49 | {
50 | MessageBox.Show("密钥不能为空!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
51 | return;
52 | }
53 | if (!Password.Any(check))
54 | {
55 | MessageBox.Show("为确保安全,密钥必须包含中文字符!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
56 | return;
57 | }
58 | var token = DataService.GetData("token");
59 | var enc = AesHelper.Encrypt(Password, token);
60 |
61 | DataService.SetData("tokenenc", enc);
62 | DataService.SetData("password", Password);
63 | NaviService.Navigate(new Page4());
64 | }
65 |
66 | public bool check(char c)
67 | {
68 | return ((int)c) > 127;
69 | }
70 |
71 | #region INotifyPropertyChanged members
72 |
73 | public event PropertyChangedEventHandler PropertyChanged;
74 |
75 | protected void RaisePropertyChanged(string propertyName)
76 | {
77 | PropertyChangedEventHandler handler = this.PropertyChanged;
78 | if (handler != null)
79 | {
80 | var e = new PropertyChangedEventArgs(propertyName);
81 | handler(this, e);
82 | }
83 | }
84 |
85 | #endregion
86 |
87 | private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
88 | {
89 | var token = DataService.GetData("token");
90 | Clipboard.SetText(token);
91 | MessageBox.Show("复制成功!");
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page4.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Pages/Page4.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using TodoSynchronizer.QuickTool.Services;
17 |
18 | namespace TodoSynchronizer.QuickTool.Pages
19 | {
20 | ///
21 | /// Page4.xaml 的交互逻辑
22 | ///
23 | public partial class Page4 : Page, INotifyPropertyChanged
24 | {
25 | public Page4()
26 | {
27 | InitializeComponent();
28 | this.DataContext = this;
29 | }
30 |
31 | private string token;
32 |
33 | public string Token
34 | {
35 | get { return token; }
36 | set
37 | {
38 | token = value;
39 | this.RaisePropertyChanged("Token");
40 | }
41 | }
42 |
43 | private string password;
44 |
45 | public string Password
46 | {
47 | get { return password; }
48 | set
49 | {
50 | password = value;
51 | this.RaisePropertyChanged("Password");
52 | }
53 | }
54 |
55 |
56 |
57 | private void Button_Click(object sender, RoutedEventArgs e)
58 | {
59 | App.Current.Shutdown();
60 | }
61 |
62 | #region INotifyPropertyChanged members
63 |
64 | public event PropertyChangedEventHandler PropertyChanged;
65 |
66 | protected void RaisePropertyChanged(string propertyName)
67 | {
68 | PropertyChangedEventHandler handler = this.PropertyChanged;
69 | if (handler != null)
70 | {
71 | var e = new PropertyChangedEventArgs(propertyName);
72 | handler(this, e);
73 | }
74 | }
75 |
76 | #endregion
77 |
78 | private void Page_Loaded(object sender, RoutedEventArgs e)
79 | {
80 | Token = DataService.GetData("tokenenc");
81 | Password = DataService.GetData("password");
82 | //Clipboard.SetText(Token);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // 有关程序集的一般信息由以下
8 | // 控制。更改这些特性值可修改
9 | // 与程序集关联的信息。
10 | [assembly: AssemblyTitle("TodoSynchronizer.QuickTool")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("TodoSynchronizer.QuickTool")]
15 | [assembly: AssemblyCopyright("Copyright © 2022")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // 将 ComVisible 设置为 false 会使此程序集中的类型
20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
21 | //请将此类型的 ComVisible 特性设置为 true。
22 | [assembly: ComVisible(false)]
23 |
24 | //若要开始生成可本地化的应用程序,请设置
25 | //.csproj 文件中的 CultureYouAreCodingWith
26 | //例如,如果您在源文件中使用的是美国英语,
27 | //使用的是美国英语,请将 设置为 en-US。 然后取消
28 | //对以下 NeutralResourceLanguage 特性的注释。 更新
29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
36 | //(未在页面中找到资源时使用,
37 | //或应用程序资源字典中找到时使用)
38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
39 | //(未在页面中找到资源时使用,
40 | //、应用程序或任何主题专用资源字典中找到时使用)
41 | )]
42 |
43 |
44 | // 程序集的版本信息由下列四个值组成:
45 | //
46 | // 主版本
47 | // 次版本
48 | // 生成号
49 | // 修订号
50 | //
51 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
52 | //通过使用 "*",如下所示:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本: 4.0.30319.42000
5 | //
6 | // 对此文件的更改可能导致不正确的行为,如果
7 | // 重新生成代码,则所做更改将丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TodoSynchronizer.QuickTool.Properties
12 | {
13 |
14 |
15 | ///
16 | /// 强类型资源类,用于查找本地化字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// 返回此类使用的缓存 ResourceManager 实例。
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TodoSynchronizer.QuickTool.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// 重写当前线程的 CurrentUICulture 属性,对
56 | /// 使用此强类型资源类的所有资源查找执行重写。
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TodoSynchronizer.QuickTool.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Services/DataService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace TodoSynchronizer.QuickTool.Services
8 | {
9 | public static class DataService
10 | {
11 | private static Dictionary _data = new Dictionary();
12 |
13 | public static T GetData(string key)
14 | {
15 | return (T)_data[key];
16 | }
17 |
18 | public static void SetData(string key, object data)
19 | {
20 | _data.Add(key, data);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Services/NaviService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 | using Wpf.Ui.Services;
8 |
9 | namespace TodoSynchronizer.QuickTool.Services
10 | {
11 | public static class NaviService
12 | {
13 | static Frame _frame;
14 | public static void SetFrame(Frame frame)
15 | {
16 | _frame = frame;
17 | _frame.Navigated += _frame_Navigated;
18 | }
19 |
20 | private static void _frame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
21 | {
22 | if (e.Content != null)
23 | {
24 | TransitionService.ApplyTransition(e.Content, TransitionType.SlideBottom, 300);
25 | }
26 | }
27 |
28 | public static void Navigate(object obj)
29 | {
30 | _frame.Navigate(obj);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Services/RenderingTier.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the MIT License.
2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors.
4 | // All Rights Reserved.
5 |
6 | namespace Wpf.Ui.Hardware;
7 |
8 | ///
9 | /// An value whose high-order word corresponds to the rendering tier for the current thread.
10 | /// Starting in the .NET Framework 4, rendering tier 1 has been redefined to only include graphics hardware that supports DirectX 9.0 or greater. Graphics hardware that supports DirectX 7 or 8 is now defined as rendering tier 0.
11 | ///
12 | public enum RenderingTier
13 | {
14 | ///
15 | /// No graphics hardware acceleration is available for the application on the device.
16 | /// All graphics features use software acceleration. The DirectX version level is less than version 9.0.
17 | ///
18 | NoAcceleration = 0x0,
19 |
20 | ///
21 | /// Most of the graphics features of WPF will use hardware acceleration
22 | /// if the necessary system resources are available and have not been exhausted.
23 | /// This corresponds to a DirectX version that is greater than or equal to 9.0.
24 | ///
25 | PartialAcceleration = 0x1,
26 |
27 | ///
28 | /// Most of the graphics features of WPF will use hardware acceleration provided the
29 | /// necessary system resources have not been exhausted.
30 | /// This corresponds to a DirectX version that is greater than or equal to 9.0.
31 | ///
32 | FullAcceleration = 0x2
33 | }
34 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/Services/TransitionType.cs:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the MIT License.
2 | // If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
3 | // Copyright (C) Leszek Pomianowski and WPF UI Contributors.
4 | // All Rights Reserved.
5 |
6 | namespace Wpf.Ui.Services;
7 |
8 | ///
9 | /// Available types of transitions.
10 | ///
11 | public enum TransitionType
12 | {
13 | ///
14 | /// None.
15 | ///
16 | None,
17 |
18 | ///
19 | /// Change opacity.
20 | ///
21 | FadeIn,
22 |
23 | ///
24 | /// Change opacity and slide from bottom.
25 | ///
26 | FadeInWithSlide,
27 |
28 | ///
29 | /// Slide from bottom.
30 | ///
31 | SlideBottom,
32 |
33 | ///
34 | /// Slide from the right side.
35 | ///
36 | SlideRight,
37 |
38 |
39 | ///
40 | /// Slide from the left side.
41 | ///
42 | SlideLeft,
43 | }
44 |
--------------------------------------------------------------------------------
/TodoSynchronizer.QuickTool/key.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/TodoSynchronizer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32505.426
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoSynchronizer", "TodoSynchronizer\TodoSynchronizer.csproj", "{A8A2A2B6-50A3-4409-88E7-B7B145C87FF6}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoSynchronizer.Core", "TodoSynchronizer.Core\TodoSynchronizer.Core.csproj", "{F45982FF-82C6-4878-AAD4-C45B6F38A950}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoSynchronizer.CLI", "TodoSynchronizer.CLI\TodoSynchronizer.CLI.csproj", "{5762C101-EF4C-47FF-AC75-E859F09AEFF2}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoSynchronizer.QuickTool", "TodoSynchronizer.QuickTool\TodoSynchronizer.QuickTool.csproj", "{3D34DEAA-BF5A-4C05-9425-9BEF1CD8F501}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {A8A2A2B6-50A3-4409-88E7-B7B145C87FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {A8A2A2B6-50A3-4409-88E7-B7B145C87FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {A8A2A2B6-50A3-4409-88E7-B7B145C87FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {A8A2A2B6-50A3-4409-88E7-B7B145C87FF6}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {F45982FF-82C6-4878-AAD4-C45B6F38A950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {F45982FF-82C6-4878-AAD4-C45B6F38A950}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {F45982FF-82C6-4878-AAD4-C45B6F38A950}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {F45982FF-82C6-4878-AAD4-C45B6F38A950}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {5762C101-EF4C-47FF-AC75-E859F09AEFF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {5762C101-EF4C-47FF-AC75-E859F09AEFF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {5762C101-EF4C-47FF-AC75-E859F09AEFF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {5762C101-EF4C-47FF-AC75-E859F09AEFF2}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {3D34DEAA-BF5A-4C05-9425-9BEF1CD8F501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {3D34DEAA-BF5A-4C05-9425-9BEF1CD8F501}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {3D34DEAA-BF5A-4C05-9425-9BEF1CD8F501}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {3D34DEAA-BF5A-4C05-9425-9BEF1CD8F501}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {C7FD366E-1255-42D8-8198-A5CF672A8961}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/TodoSynchronizer/App.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 微软雅黑
24 | 微软雅黑
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/TodoSynchronizer/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Configuration;
5 | using System.Data;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 |
11 | namespace TodoSynchronizer
12 | {
13 | ///
14 | /// Interaction logic for App.xaml
15 | ///
16 | public partial class App : Application
17 | {
18 | protected override void OnStartup(StartupEventArgs e)
19 | {
20 | base.OnStartup(e);
21 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore };
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TodoSynchronizer/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Controls/ClippingBorder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Media;
9 |
10 | namespace TodoSynchronizer.Controls
11 | {
12 | public class ClippingBorder : Border
13 | {
14 | protected override void OnRender(DrawingContext dc)
15 | {
16 | OnApplyChildClip();
17 | base.OnRender(dc);
18 | }
19 |
20 | public override UIElement Child
21 | {
22 | get
23 | {
24 | return base.Child;
25 | }
26 | set
27 | {
28 | if (this.Child != value)
29 | {
30 | if (this.Child != null)
31 | {
32 | // Restore original clipping
33 | this.Child.SetValue(UIElement.ClipProperty, _oldClip);
34 | }
35 |
36 | if (value != null)
37 | {
38 | _oldClip = value.ReadLocalValue(UIElement.ClipProperty);
39 | }
40 | else
41 | {
42 | // If we dont set it to null we could leak a Geometry object
43 | _oldClip = null;
44 | }
45 |
46 | base.Child = value;
47 | }
48 | }
49 | }
50 |
51 | protected virtual void OnApplyChildClip()
52 | {
53 | UIElement child = this.Child;
54 | if (child != null)
55 | {
56 | _clipRect.RadiusX = _clipRect.RadiusY = Math.Max(0.0, this.CornerRadius.TopLeft - (this.BorderThickness.Left * 0.5));
57 | Rect rect = new Rect(this.RenderSize);
58 | rect.Height -= (this.BorderThickness.Top + this.BorderThickness.Bottom);
59 | rect.Width -= (this.BorderThickness.Left + this.BorderThickness.Right);
60 | _clipRect.Rect = rect;
61 | child.Clip = _clipRect;
62 | }
63 | }
64 |
65 | public void Update() { OnApplyChildClip(); }
66 |
67 | private RectangleGeometry _clipRect = new RectangleGeometry();
68 | private object _oldClip;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Converters/BoolToAppearanceConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 |
9 | namespace TodoSynchronizer.Converters
10 | {
11 | public class BoolToAppearanceConverter : IValueConverter
12 | {
13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14 | {
15 | bool islogin = (bool)value;
16 | if (value == null)
17 | return Wpf.Ui.Common.ControlAppearance.Caution;
18 | if (islogin)
19 | return Wpf.Ui.Common.ControlAppearance.Primary;
20 | else
21 | return Wpf.Ui.Common.ControlAppearance.Caution;
22 | }
23 |
24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
25 | {
26 | throw new NotImplementedException();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Converters/RevBooleanToVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace TodoSynchronizer.Converters
7 | {
8 | public class RevBooleanToVisibilityConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | return value != null && (bool) value ? Visibility.Collapsed : Visibility.Visible;
13 | }
14 |
15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
16 | {
17 | throw new NotSupportedException();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Extensions/ClickExt.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using Microsoft.Xaml.Behaviors;
9 |
10 | namespace TodoSynchronizer.Extensions
11 | {
12 | public class ClickExtensions
13 | {
14 | public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent
15 | ("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ClickExtensions));
16 |
17 | // 为界面元素添加路由事件侦听
18 | public static void AddClickHandler(DependencyObject d, RoutedEventHandler h)
19 | {
20 | UIElement e = d as UIElement;
21 | if (e != null)
22 | {
23 | e.AddHandler(ClickExtensions.ClickEvent, h);
24 | }
25 | }
26 |
27 | // 移除侦听
28 | public static void RemoveClickHandler(DependencyObject d, RoutedEventHandler h)
29 | {
30 | UIElement e = d as UIElement;
31 | if (e != null)
32 | {
33 | e.RemoveHandler(ClickExtensions.ClickEvent, h);
34 | }
35 | }
36 | }
37 |
38 | public class ClickBehavior : Behavior
39 | {
40 | int status = 0;
41 | protected override void OnAttached()
42 | {
43 | base.OnAttached();
44 | AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
45 | AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
46 | AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
47 | AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
48 | }
49 |
50 | private void AssociatedObject_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
51 | {
52 | if (status > 0)
53 | {
54 | RoutedEventArgs arg = new RoutedEventArgs(ClickExtensions.ClickEvent, AssociatedObject);
55 | AssociatedObject.RaiseEvent(arg);
56 | }
57 | status = 0;
58 | }
59 |
60 | private void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
61 | {
62 | status = 1;
63 | }
64 |
65 | private void AssociatedObject_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
66 | {
67 | status = 0;
68 | }
69 |
70 | private void AssociatedObject_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
71 | {
72 | if (status == 0)
73 | status = -1;
74 | }
75 |
76 | protected override void OnDetaching()
77 | {
78 | base.OnDetaching();
79 | AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
80 | AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;
81 | AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
82 | AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Helpers/AnimationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Media.Animation;
8 |
9 | namespace TodoSynchronizer.Helpers
10 | {
11 | public class AnimationHelper
12 | {
13 | public static DoubleAnimationUsingKeyFrames CubicBezierDoubleAnimation(TimeSpan d, double s, double t, string Bezier)
14 | {
15 | DoubleKeyFrame dkf = new LinearDoubleKeyFrame();
16 | dkf.KeyTime = TimeSpan.FromSeconds(0.0);
17 | dkf.Value = s;
18 | SplineDoubleKeyFrame sp = new SplineDoubleKeyFrame();
19 | sp.KeyTime = d;
20 | string[] p = Bezier.Split(new char[]
21 | {
22 | ','
23 | });
24 | Point controlPoint = new Point(Double.Parse(p[0]), Double.Parse(p[1]));
25 | Point controlPoint2 = new Point(Double.Parse(p[2]), Double.Parse(p[3]));
26 | sp.KeySpline = new KeySpline
27 | {
28 | ControlPoint1 = controlPoint,
29 | ControlPoint2 = controlPoint2
30 | };
31 | sp.Value = t;
32 | return new DoubleAnimationUsingKeyFrames
33 | {
34 | KeyFrames =
35 | {
36 | dkf,
37 | sp
38 | },
39 | FillBehavior = FillBehavior.HoldEnd
40 | };
41 | }
42 |
43 | public static DoubleAnimationUsingKeyFrames CubicBezierDoubleAnimation(TimeSpan d, double t, string Bezier)
44 | {
45 | SplineDoubleKeyFrame sp = new SplineDoubleKeyFrame();
46 | sp.KeyTime = d;
47 | string[] p = Bezier.Split(new char[]
48 | {
49 | ','
50 | });
51 | Point controlPoint = new Point(Double.Parse(p[0]), Double.Parse(p[1]));
52 | Point controlPoint2 = new Point(Double.Parse(p[2]), Double.Parse(p[3]));
53 | sp.KeySpline = new KeySpline
54 | {
55 | ControlPoint1 = controlPoint,
56 | ControlPoint2 = controlPoint2
57 | };
58 | sp.Value = t;
59 | return new DoubleAnimationUsingKeyFrames
60 | {
61 | KeyFrames =
62 | {
63 | sp
64 | },
65 | FillBehavior = FillBehavior.HoldEnd
66 | };
67 | }
68 |
69 | public static DoubleAnimationUsingKeyFrames CubicBezierDoubleAnimation(TimeSpan st, TimeSpan d, double s, double t, string Bezier)
70 | {
71 | DoubleKeyFrame dkf = new LinearDoubleKeyFrame();
72 | dkf.KeyTime = TimeSpan.FromSeconds(0.0);
73 | dkf.Value = s;
74 | DoubleKeyFrame dkf2 = new LinearDoubleKeyFrame();
75 | dkf2.KeyTime = st;
76 | dkf2.Value = s;
77 | SplineDoubleKeyFrame sp = new SplineDoubleKeyFrame();
78 | sp.KeyTime = st + d;
79 | string[] p = Bezier.Split(new char[]
80 | {
81 | ','
82 | });
83 | Point controlPoint = new Point(Double.Parse(p[0]), Double.Parse(p[1]));
84 | Point controlPoint2 = new Point(Double.Parse(p[2]), Double.Parse(p[3]));
85 | sp.KeySpline = new KeySpline
86 | {
87 | ControlPoint1 = controlPoint,
88 | ControlPoint2 = controlPoint2
89 | };
90 | sp.Value = t;
91 | return new DoubleAnimationUsingKeyFrames
92 | {
93 | KeyFrames =
94 | {
95 | dkf,
96 | dkf2,
97 | sp
98 | },
99 | FillBehavior = FillBehavior.HoldEnd
100 | };
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Helpers/BitmapHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Drawing.Imaging;
4 | using System.IO;
5 | using System.Windows;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Imaging;
8 |
9 | namespace TodoSynchronizer.Helpers
10 | {
11 | public class BitmapHelper
12 | {
13 | public static BitmapImage GetBitmapImage(Bitmap bitmap)
14 | {
15 | BitmapImage bi = new BitmapImage();
16 | bi.BeginInit();
17 | MemoryStream ms = new MemoryStream();
18 | bitmap.Save(ms, ImageFormat.Bmp);
19 | ms.Seek(0, SeekOrigin.Begin);
20 | bi.StreamSource = ms;
21 | bi.EndInit();
22 | return bi;
23 | }
24 |
25 | public static Bitmap CaptureScreenToBitmap(double x, double y, double width, double height)
26 | {
27 | int ix = Convert.ToInt32(x);
28 | int iy = Convert.ToInt32(y);
29 | int iw = Convert.ToInt32(width);
30 | int ih = Convert.ToInt32(height);
31 |
32 | Bitmap bitmap = new Bitmap(iw, ih);
33 | using (Graphics graphics = Graphics.FromImage(bitmap))
34 | {
35 | graphics.CopyFromScreen(ix, iy, 0, 0, new System.Drawing.Size(iw, ih));
36 | return bitmap;
37 |
38 | }
39 | }
40 |
41 | public static BitmapSource GetBitmapSource(Bitmap bmp)
42 | {
43 | BitmapFrame bf = null;
44 |
45 | using (MemoryStream ms = new MemoryStream())
46 | {
47 | bmp.Save(ms, ImageFormat.Png);
48 | bf = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
49 |
50 | }
51 | return bf;
52 |
53 | }
54 |
55 | public static BitmapSource GetBitmapSource(Stream stream)
56 | {
57 | BitmapFrame bf = null;
58 | bf = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
59 | return bf;
60 |
61 | }
62 |
63 | public static Bitmap GetBitmap(BitmapSource source)
64 | {
65 | using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
66 | {
67 | BitmapEncoder encoder = new BmpBitmapEncoder();
68 | encoder.Frames.Add(BitmapFrame.Create(source));
69 | encoder.Save(ms);
70 | return new Bitmap(ms);
71 | }
72 | }
73 |
74 | public static Bitmap RenderVisual(UIElement elt, int x, int y)
75 | {
76 | PresentationSource source = PresentationSource.FromVisual(elt);
77 | RenderTargetBitmap rtb = new RenderTargetBitmap(x, y, 96, 96, PixelFormats.Default);
78 | VisualBrush sourceBrush = new VisualBrush(elt);
79 | DrawingVisual drawingVisual = new DrawingVisual();
80 | DrawingContext drawingContext = drawingVisual.RenderOpen();
81 |
82 | using (drawingContext)
83 | drawingContext.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new System.Windows.Point(x, y)));
84 |
85 | rtb.Render(drawingVisual);
86 | MemoryStream stream = new MemoryStream();
87 | BitmapEncoder encoder = new BmpBitmapEncoder();
88 | encoder.Frames.Add(BitmapFrame.Create(rtb));
89 | encoder.Save(stream);
90 |
91 | return new Bitmap(stream);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Helpers/IniHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualBasic;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace TodoSynchronizer.Helpers
11 | {
12 | public static class IniHelper
13 | {
14 | [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
15 | private static extern int WritePrivateProfileString(string section, string key, string val, string filePath);
16 |
17 | [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
18 | private static extern int WritePrivateProfileString(string section, string val, string filePath);
19 |
20 | [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
21 | private static extern int WritePrivateProfileSection(string section, string val, string filePath);
22 |
23 | [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
24 | public static extern int GetPrivateProfileString(string lpApplicationName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName);
25 |
26 | public static string GetKeyValue(string Section, string Key, string DefaultText)
27 | {
28 | string iniFilePath = IniHelper.inipath;
29 | int BufferSize = 9999;
30 | StringBuilder keyValue = new StringBuilder(BufferSize);
31 | string text = "";
32 | int Rvalue = GetPrivateProfileString(Section, Key, text, keyValue, BufferSize, iniFilePath);
33 |
34 | bool flag = Rvalue == 0;
35 | if (flag)
36 | {
37 | return DefaultText;
38 | }
39 | else
40 | {
41 | return keyValue.ToString();
42 | }
43 | }
44 |
45 | public static bool SetKeyValue(string Section, string Key, string Value)
46 | {
47 | string iniFilePath = IniHelper.inipath;
48 | string pat = Path.GetDirectoryName(iniFilePath);
49 | bool flag = !Directory.Exists(pat);
50 | if (flag)
51 | {
52 | Directory.CreateDirectory(pat);
53 | }
54 | bool flag2 = !File.Exists(iniFilePath);
55 | if (flag2)
56 | {
57 | File.Create(iniFilePath).Close();
58 | }
59 | int OpStation = IniHelper.WritePrivateProfileString(Section, Key, Value, iniFilePath);
60 | bool flag3 = OpStation == 0L;
61 | return !flag3;
62 | }
63 |
64 | public static bool DelKeyValue(string Section, string Key)
65 | {
66 | string iniFilePath = IniHelper.inipath;
67 | int OpStation = IniHelper.WritePrivateProfileString(Section, Key, null, iniFilePath);
68 | bool flag3 = OpStation == 0L;
69 | return !flag3;
70 | }
71 |
72 | public static bool DelSection(string Section)
73 | {
74 | string iniFilePath = IniHelper.inipath;
75 | int OpStation = IniHelper.WritePrivateProfileSection(Section, null, iniFilePath);
76 | bool flag3 = OpStation == 0L;
77 | return !flag3;
78 | }
79 |
80 | public static string inipath = Environment.GetEnvironmentVariable("LocalAppData") + "\\TodoSynchronizer\\Settings.ini";
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Helpers/MsalHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Identity.Client;
2 | using Microsoft.Identity.Client.Broker;
3 | //using Microsoft.Identity.Client.Desktop;
4 | using Microsoft.Identity.Client.Extensions.Msal;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Interop;
13 | using TodoSynchronizer.Core.Models;
14 |
15 | namespace TodoSynchronizer.Helpers
16 | {
17 | public class MsalHelper
18 | {
19 | public MsalHelper()
20 | {
21 | CreateApplication();
22 | }
23 |
24 | public void CreateApplication()
25 | {
26 | _clientApp = PublicClientApplicationBuilder.Create(ClientId)
27 | .WithAuthority($"{Instance}{Tenant}")
28 | .WithDefaultRedirectUri()
29 | //.WithBrokerPreview(true)
30 | .Build();
31 | var storageProperties =
32 | new StorageCreationPropertiesBuilder(CacheFileName, CacheDir)
33 | //.WithUnprotectedFile()
34 | .Build();
35 |
36 | var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).Result;
37 | cacheHelper.RegisterCache(_clientApp.UserTokenCache);
38 | }
39 |
40 | public async Task GetToken(Window host)
41 | {
42 | AuthenticationResult authResult = null;
43 | var app = PublicClientApp;
44 | IAccount firstAccount;
45 |
46 | var accounts = await app.GetAccountsAsync().ConfigureAwait(false);
47 | firstAccount = accounts.FirstOrDefault();
48 |
49 | try
50 | {
51 | authResult = await app.AcquireTokenSilent(scopes, firstAccount)
52 | .ExecuteAsync();
53 | }
54 | catch (MsalUiRequiredException ex)
55 | {
56 | // A MsalUiRequiredException happened on AcquireTokenSilent.
57 | // This indicates you need to call AcquireTokenInteractive to acquire a token
58 | System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
59 |
60 | try
61 | {
62 | authResult = await app.AcquireTokenInteractive(scopes)
63 | .WithAccount(firstAccount)
64 | .WithParentActivityOrWindow(new WindowInteropHelper(host).Handle)
65 | .WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
66 | .ExecuteAsync();
67 | }
68 | catch (MsalException msalex)
69 | {
70 | return new CommonResult(false, $"Error Acquiring Token:{System.Environment.NewLine}{msalex}");
71 | }
72 | }
73 | catch (Exception ex)
74 | {
75 | return new CommonResult(false, $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}");
76 | }
77 |
78 | if (authResult != null)
79 | {
80 | return new CommonResult(true, authResult.AccessToken);
81 | }
82 | else
83 | {
84 | return new CommonResult(false, "未知错误");
85 | }
86 | }
87 |
88 |
89 | private static string ClientId = "49694ef2-8751-4ac9-8431-8817c27350b4";
90 |
91 | private static string Tenant = "common";
92 | private static string Instance = "https://login.microsoftonline.com/";
93 | private IPublicClientApplication _clientApp;
94 |
95 | public IPublicClientApplication PublicClientApp { get { return _clientApp; } }
96 |
97 | private static readonly string s_cacheFilePath =
98 | Path.Combine(MsalCacheHelper.UserRootDirectory, "msal.contoso.cache");
99 |
100 | public static readonly string CacheFileName = Path.GetFileName(s_cacheFilePath);
101 | public static readonly string CacheDir = Path.GetDirectoryName(s_cacheFilePath);
102 |
103 | public static string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
104 |
105 | public static string[] scopes = new string[] { "Tasks.ReadWrite", "offline_access", "User.Read" };
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Helpers/TransformHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Media;
7 |
8 | namespace TodoSynchronizer.Helpers
9 | {
10 | public static class TransformHelper
11 | {
12 | public static ScaleTransform FindScaleTransform(Transform hayStack)
13 | {
14 | if (hayStack is ScaleTransform)
15 | return (ScaleTransform)hayStack;
16 |
17 | if (hayStack is TransformGroup)
18 | {
19 | TransformGroup group = hayStack as TransformGroup;
20 |
21 | foreach (var child in group.Children)
22 | {
23 | if (child is ScaleTransform)
24 | return (ScaleTransform)child;
25 | }
26 | }
27 |
28 | return null/* TODO Change to default(_) if this is not a reference type */;
29 | }
30 | public static RotateTransform FindRotateTransform(Transform hayStack)
31 | {
32 | if (hayStack is RotateTransform)
33 | return (RotateTransform)hayStack;
34 |
35 | if (hayStack is TransformGroup)
36 | {
37 | TransformGroup group = hayStack as TransformGroup;
38 |
39 | foreach (var child in group.Children)
40 | {
41 | if (child is RotateTransform)
42 | return (RotateTransform)child;
43 | }
44 | }
45 | return null/* TODO Change to default(_) if this is not a reference type */;
46 | }
47 | public static TranslateTransform FindTranslateTransform(Transform hayStack)
48 | {
49 | if (hayStack is TranslateTransform)
50 | return (TranslateTransform)hayStack;
51 |
52 | if (hayStack is TransformGroup)
53 | {
54 | TransformGroup group = hayStack as TransformGroup;
55 |
56 | foreach (var child in group.Children)
57 | {
58 | if (child is TranslateTransform)
59 | return (TranslateTransform)child;
60 | }
61 | }
62 | return null;
63 | }
64 |
65 | internal static TranslateTransform CreateTranslateTransform(Transform hayStack)
66 | {
67 | if (hayStack is TranslateTransform)
68 | return (TranslateTransform)hayStack;
69 |
70 | if (hayStack is TransformGroup)
71 | {
72 | TransformGroup group = hayStack as TransformGroup;
73 |
74 | foreach (var child in group.Children)
75 | {
76 | if (child is TranslateTransform)
77 | return (TranslateTransform)child;
78 | }
79 |
80 | var tt = new TranslateTransform();
81 | group.Children.Add(tt);
82 | return tt;
83 | }
84 | return null;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Models/LoginInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Media.Imaging;
7 |
8 | namespace TodoSynchronizer.Models
9 | {
10 | public class LoginInfoModel
11 | {
12 | public string UserName { get; set; }
13 | public string UserEmail { get; set; }
14 | public BitmapSource UserAvatar { get; set; }
15 | public bool IsLogin { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Mvvm/RoutedEventTrigger.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Xaml.Behaviors;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TodoSynchronizer.Mvvm
10 | {
11 | public class RoutedEventTrigger : EventTriggerBase
12 | {
13 | RoutedEvent _routedEvent;
14 | public RoutedEvent RoutedEvent
15 | {
16 | get { return _routedEvent; }
17 | set { _routedEvent = value; }
18 | }
19 |
20 | public RoutedEventTrigger() { }
21 |
22 | protected override void OnAttached()
23 | {
24 | if (RoutedEvent != null)
25 | {
26 | Source.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
27 | }
28 | }
29 |
30 | void OnRoutedEvent(object sender, RoutedEventArgs args)
31 | {
32 | base.OnEvent(args);
33 | }
34 | protected override string GetEventName()
35 | {
36 | return RoutedEvent.Name;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "TodoSynchronizer": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-local"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/TodoSynchronizer/Resources/Icons.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Resources/cloud.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Resources/todo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Resources/todosync.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/TodoSynchronizer/Resources/todosync.ico
--------------------------------------------------------------------------------
/TodoSynchronizer/TodoSynchronizer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 |
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | $(DefaultXamlRuntime)
40 | Designer
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/TodoSynchronizer/UnitTest/Styles/CanvasTemplate.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/TodoSynchronizer/UnitTest/Styles/CanvasTemplateSelector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using TodoSynchronizer.Core.Models.CanvasModels;
9 |
10 | namespace TodoSynchronizer.UnitTest
11 | {
12 | public class CanvasTemplateSelector : DataTemplateSelector
13 | {
14 | public DataTemplate CourseTemplate { get; set; }
15 | public DataTemplate AssignmentTemplate { get; set; }
16 | public DataTemplate QuizTemplate { get; set; }
17 | public DataTemplate AnouncementTemplate { get; set; }
18 | public DataTemplate DiscussionTemplate { get; set; }
19 | public override DataTemplate SelectTemplate(object obj, DependencyObject container)
20 | {
21 | if (obj == null) return null;
22 | if (obj is Course) return CourseTemplate;
23 | if (obj is Assignment) return AssignmentTemplate;
24 | if (obj is Quiz) return QuizTemplate;
25 | if (obj is Anouncement) return AnouncementTemplate;
26 | if (obj is Discussion) return DiscussionTemplate;
27 | return null;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TodoSynchronizer/UnitTest/Styles/TodoListBoxStyle.xaml:
--------------------------------------------------------------------------------
1 |
4 |
25 |
69 |
--------------------------------------------------------------------------------
/TodoSynchronizer/UnitTest/Styles/TodoTemplate.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/TodoSynchronizer/UnitTest/Styles/TodoTemplateSelector.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Graph;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 |
10 | namespace TodoSynchronizer.UnitTest
11 | {
12 | public class TodoTemplateSelector : DataTemplateSelector
13 | {
14 | public DataTemplate TodoListTemplate { get; set; }
15 | public DataTemplate TodoItemTemplate { get; set; }
16 | public DataTemplate TodoLinkedResourceTemplate { get; set; }
17 | public DataTemplate TodoCheckItemTemplate { get; set; }
18 | public DataTemplate TodoAttachmentTemplate { get; set; }
19 | public override DataTemplate SelectTemplate(object obj, DependencyObject container)
20 | {
21 | if (obj == null) return null;
22 | if (obj is TodoTaskList) return TodoListTemplate;
23 | if (obj is TodoTask) return TodoItemTemplate;
24 | if (obj is ChecklistItem) return TodoCheckItemTemplate;
25 | if (obj is LinkedResource) return TodoLinkedResourceTemplate;
26 | if (obj is AttachmentBase) return TodoAttachmentTemplate;
27 | return null;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TodoSynchronizer/ViewModels/CanvasLoginViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using CommunityToolkit.Mvvm.Input;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows.Media.Imaging;
9 | using TodoSynchronizer.Core.Models;
10 | using TodoSynchronizer.Core.Services;
11 | using TodoSynchronizer.Helpers;
12 | using TodoSynchronizer.Models;
13 | using TodoSynchronizer.Views;
14 |
15 | namespace TodoSynchronizer.ViewModels
16 | {
17 | public partial class CanvasLoginViewModel : ObservableObject
18 | {
19 | [ObservableProperty]
20 | private LoginInfoModel loginInfo = new LoginInfoModel();
21 |
22 | public RelayCommand LoginCommand { get; set; }
23 | public RelayCommand LogoutCommand { get; set; }
24 | public RelayCommand SwitchCommand { get; set; }
25 |
26 | public CanvasLoginViewModel()
27 | {
28 | LoginCommand = new RelayCommand(Login);
29 | LogoutCommand = new RelayCommand(Logout);
30 | SwitchCommand = new RelayCommand(Switch);
31 | }
32 |
33 | public void Login()
34 | {
35 | var token = IniHelper.GetKeyValue("canvas", "token", "");
36 | if (token != "")
37 | {
38 | var res1 = CanvasService.Login(token);
39 | if (res1.success)
40 | {
41 | CanvasLoginSuccess();
42 | return;
43 | }
44 | }
45 |
46 | var m = new CanvasLoginWindow();
47 | var res2 = m.ShowDialog();
48 | if (res2.Value == true)
49 | {
50 | CanvasLoginSuccess();
51 | return;
52 | }
53 | else
54 | {
55 | return;
56 | }
57 | }
58 |
59 | public void CanvasLoginSuccess()
60 | {
61 | var info = new LoginInfoModel();
62 | info.UserName = CanvasService.User.Name;
63 | info.UserEmail = CanvasService.User.PrimaryEmail;
64 |
65 | BitmapImage bitImage = new BitmapImage();
66 | bitImage.BeginInit();
67 | bitImage.UriSource = new Uri(CanvasService.User.AvatarUrl, UriKind.Absolute);
68 | bitImage.EndInit();
69 | info.UserAvatar = bitImage;
70 | info.IsLogin = true;
71 | LoginInfo = info;
72 | }
73 |
74 | public void Logout()
75 | {
76 | LoginInfo = new LoginInfoModel();
77 | }
78 |
79 | public void Switch()
80 | {
81 |
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/TodoSynchronizer/ViewModels/TodoLoginViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using TodoSynchronizer.Helpers;
8 | using TodoSynchronizer.Core.Models;
9 | using TodoSynchronizer.Mvvm;
10 | using TodoSynchronizer.Core.Services;
11 | using CommunityToolkit.Mvvm.ComponentModel;
12 | using CommunityToolkit.Mvvm.Input;
13 | using TodoSynchronizer.Models;
14 |
15 | namespace TodoSynchronizer.ViewModels
16 | {
17 | public partial class TodoLoginViewModel : ObservableObject
18 | {
19 | [ObservableProperty]
20 |
21 | private LoginInfoModel loginInfo = new LoginInfoModel();
22 | public AsyncRelayCommand LoginCommand { get; set; }
23 | public RelayCommand LogoutCommand { get; set; }
24 | public RelayCommand SwitchCommand { get; set; }
25 |
26 | public TodoLoginViewModel()
27 | {
28 | LoginCommand = new AsyncRelayCommand(Login);
29 | LogoutCommand = new RelayCommand(Logout);
30 | SwitchCommand = new RelayCommand(Switch);
31 | }
32 |
33 | public async Task Login()
34 | {
35 | MsalHelper helper = new MsalHelper();
36 | CommonResult res = await helper.GetToken(Application.Current.MainWindow);
37 |
38 | if (!res.success)
39 | {
40 | MessageBox.Show(res.result);
41 | return;
42 | }
43 | TodoService.Token = res.result;
44 | var userinfo = TodoService.GetUserInfo();
45 | var logininfo = new LoginInfoModel();
46 |
47 | logininfo.UserName = userinfo.DisplayName;
48 | logininfo.UserEmail = userinfo.UserPrincipalName;
49 | logininfo.UserAvatar = BitmapHelper.GetBitmapSource(TodoService.GetUserAvatar());
50 | logininfo.IsLogin = true;
51 | LoginInfo = logininfo;
52 | }
53 | public void Logout()
54 | {
55 | LoginInfo = new LoginInfoModel();
56 | }
57 | public void Switch()
58 | {
59 |
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/CanvasLoginWindow.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
17 |
20 | 请登录Canvas LMS
21 |
22 |
23 |
24 |
30 |
37 |
43 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/CanvasLoginWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Shapes;
15 | using TodoSynchronizer.Extensions;
16 | using TodoSynchronizer.Helpers;
17 | using TodoSynchronizer.Core.Models;
18 | using TodoSynchronizer.Core.Services;
19 | using Wpf.Ui.Controls;
20 | using Wpf.Ui.Extensions;
21 |
22 | namespace TodoSynchronizer.Views
23 | {
24 | ///
25 | /// CanvasLoginWindow.xaml 的交互逻辑
26 | ///
27 | public partial class CanvasLoginWindow : Window, INotifyPropertyChanged
28 | {
29 | public CanvasLoginWindow()
30 | {
31 | InitializeComponent();
32 | this.DataContext = this;
33 |
34 | }
35 |
36 | private string errorText;
37 | public string ErrorText
38 | {
39 | get { return errorText; }
40 | set
41 | {
42 | errorText = value;
43 | this.RaisePropertyChanged("ErrorText");
44 | }
45 | }
46 | private string token;
47 | public string Token
48 | {
49 | get { return token; }
50 | set
51 | {
52 | token = value;
53 | this.RaisePropertyChanged("Token");
54 | }
55 | }
56 |
57 | private BackgroundWorker worker;
58 |
59 | private void TextHelp_MouseDown(object sender, MouseButtonEventArgs e)
60 | {
61 |
62 | }
63 |
64 | private void LoginButton_Click(object sender, RoutedEventArgs e)
65 | {
66 | worker = new BackgroundWorker();
67 | worker.DoWork += Login_DoWork;
68 | worker.RunWorkerCompleted += Login_RunWorkerCompleted;
69 | worker.RunWorkerAsync();
70 | }
71 |
72 | private void Login_DoWork(object sender, DoWorkEventArgs e)
73 | {
74 | this.Dispatcher.Invoke(() => {
75 | ButtonProgressAssist.SetIsIndeterminate(CookieLoginButton, true);
76 | ButtonProgressAssist.SetIsIndicatorVisible(CookieLoginButton, true);
77 | CookieLoginButton.Content = "正在登录...";
78 | this.IsEnabled = false;
79 | });
80 |
81 | e.Result = CanvasService.Login(Token);
82 | }
83 |
84 | private void Login_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
85 | {
86 | var res = (CommonResult)e.Result;
87 | this.Dispatcher.Invoke(() => {
88 | ButtonProgressAssist.SetIsIndeterminate(CookieLoginButton, false);
89 | ButtonProgressAssist.SetIsIndicatorVisible(CookieLoginButton, false);
90 | CookieLoginButton.Content = "登录";
91 | this.IsEnabled = true;
92 | });
93 | this.IsEnabled = true;
94 | if (res.success)
95 | {
96 | IniHelper.SetKeyValue("canvas", "token", Token);
97 | this.Dispatcher.Invoke(() => {
98 | this.DialogResult = true;
99 | this.Close();
100 | });
101 | }
102 | else
103 | {
104 | ErrorTextBlock.Visibility = Visibility.Visible;
105 | ErrorText = res.result;
106 | }
107 | }
108 |
109 | #region INotifyPropertyChanged members
110 |
111 | public event PropertyChangedEventHandler PropertyChanged;
112 | protected void RaisePropertyChanged(string propertyName)
113 | {
114 | PropertyChangedEventHandler handler = this.PropertyChanged;
115 | if (handler != null)
116 | {
117 | var e = new PropertyChangedEventArgs(propertyName);
118 | handler(this, e);
119 | }
120 | }
121 | #endregion
122 |
123 | private void Window_Closed(object sender, EventArgs e)
124 | {
125 |
126 | }
127 |
128 | private void Window_Loaded(object sender, RoutedEventArgs e)
129 | {
130 | //this.DialogResult = false;
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/AnouncementPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/AnouncementPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// AnouncementPage.xaml 的交互逻辑
20 | ///
21 | public partial class AnouncementPage : Page
22 | {
23 | public AnouncementPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/AssignmentPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/AssignmentPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// AssignmentPage.xaml 的交互逻辑
20 | ///
21 | public partial class AssignmentPage : Page
22 | {
23 | public AssignmentPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/DiscussionPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/DiscussionPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// DiscussionPage.xaml 的交互逻辑
20 | ///
21 | public partial class DiscussionPage : Page
22 | {
23 | public DiscussionPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/GeneralPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/GeneralPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// GeneralPage.xaml 的交互逻辑
20 | ///
21 | public partial class GeneralPage : Page
22 | {
23 | public GeneralPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/QuizPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/QuizPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// QuizPage.xaml 的交互逻辑
20 | ///
21 | public partial class QuizPage : Page
22 | {
23 | public QuizPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/UIPage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/Pages/UIPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace TodoSynchronizer.Views.Pages
17 | {
18 | ///
19 | /// UIPage.xaml 的交互逻辑
20 | ///
21 | public partial class UIPage : Page
22 | {
23 | public UIPage()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/SettingsWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
38 |
41 |
44 |
47 |
50 |
53 |
54 |
55 |
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/TodoSynchronizer/Views/SettingsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 |
15 | namespace TodoSynchronizer.Views
16 | {
17 | ///
18 | /// SettingsWindow.xaml 的交互逻辑
19 | ///
20 | public partial class SettingsWindow : Window
21 | {
22 | public SettingsWindow()
23 | {
24 | InitializeComponent();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docs/actions-persis.md:
--------------------------------------------------------------------------------
1 | ## 配置步骤
2 | ### 一、配置 Canvas Token
3 |
4 | 1. 点击右上角的 “fork” 复制这个仓库,注意**取消勾选**“Copy the master branch only”(若没注册过 GitHub 账号,按照指示注册一个)
5 |
6 | 
7 |
8 | 2. 打开 Canvas,进入“设置”页面
9 |
10 | 
11 |
12 | 3. 点击“创建新访问许可证”
13 |
14 | 
15 |
16 | 4. “用途”随便填一个,“过期”建议留空,点击“生成令牌”
17 |
18 | 
19 |
20 | 5. 复制生成的令牌
21 |
22 | 
23 |
24 | 6. 在**自己 fork 的仓库**,进入“Settings”
25 |
26 | 
27 |
28 | 7. 选择“Secrets - Actions”,点击“New repository secret”
29 |
30 | 
31 |
32 | 8. “Name”为“CANVAS_TOKEN”,“Value”为刚才复制的令牌
33 |
34 | 
35 |
36 | 9. 添加 Canvas Token 完成
37 |
38 | 
39 |
40 | ### 二、配置 Graph Token(若要同步到 Microsoft Todo)
41 |
42 | 下面以 Windows 系统为例,借助“Graph 认证辅助工具”(在本仓库开源)进行配置。如有其它需求,可参考[手动配置 Graph Token](./graph-token-manually.md)
43 |
44 | 10. 在 [Releases](../../../releases) 页面下载“TodoSynchronizer.QuickTool.exe”,打开。
45 |
46 | 11. 点击“获取 Graph 认证”,跳转到浏览器
47 |
48 | 12. 授权 MyTodoApp(务必核对权限是否与图中一致)
49 |
50 | 
51 |
52 | 13. 完成后,回到程序,输入密钥以保护用户凭证
53 |
54 | 
55 | 
56 |
57 | 1. 在自己 fork 的仓库,切换到 graphtoken 分支
58 |
59 | 
60 |
61 | 15. 编辑 graphtoken.asc 文件
62 |
63 | 
64 |
65 | 16. 粘贴刚才复制的内容(把原先的**覆盖**掉,注意不要有多余的换行),直接提交
66 |
67 | 
68 |
69 | 17. 再次到“Secrets”里,创建一个新条目,“Name”为“KEY”,“Value”为刚才输入的密钥
70 |
71 | 
72 |
73 | ### 三、配置滴答清单登录信息(若要同步到 滴答清单)
74 | 18. 在**自己 fork 的仓库**,进入“Settings”
75 |
76 | 
77 |
78 | 19. 选择“Secrets - Actions”,点击“New repository secret”
79 |
80 | 
81 |
82 | 20. “Name”为“DIDA_CREDENTIAL”,“Value”按照下面的格式填写
83 | ```
84 | {"phone":"你的手机号","password":"你的密码"}
85 | ```
86 |
87 | 
88 |
89 | 21. 添加滴答清单登录信息完成
90 |
91 | 
92 |
93 |
94 | ### 三、启动定时任务
95 |
96 | 22. 点击“Actions”选项卡,点击按钮启用
97 |
98 | 
99 |
100 | 23. 左侧选择合适的项目,右侧点击“Run workflow”
101 |
102 | 
103 | 
104 |
105 | 24. 刷新,可进入 Action 查看详情。Run 步骤下面的内容为程序的执行输出
106 |
107 | 
108 |
109 | 25. 编辑仓库根目录下的 `config.yaml` 文件,可以调整同步程序设置
110 |
111 | 
112 |
113 | ### Q&A
114 | #### Fork 的仓库能不能设置为 Private?
115 |
116 | 对于公开仓库,GitHub Actions 是免费的;而对于私有仓库,GitHub Actions 每个月有 2000 分钟的免费额度,超出会有巨额收费(在[这里](https://github.com/settings/billing)看用量)。本项目每次运行大约需要 2min,也就是说每个月顶多能运行 1000 次,可能不够用。**强烈建议保持仓库为公开状态**。
117 |
118 | #### 为什么步骤这么复杂?我的账号、Token 安全吗?
119 |
120 | 复杂的配置步骤就是为了保证账号和令牌的安全性。Canvas Token 的安全性由 GitHub 保证,Graph Token 的安全性由 AES 算法保证。
121 |
122 | #### 为什么不直接把 Graph 的 AccessToken/RefreshToken 保存到 Secrets,像 Canvas Token 那样?
123 |
124 | AccessToken 的有效期只有 1 小时,RefreshToken 的有效期可能是 90 天(参考[这里](https://docs.microsoft.com/zh-cn/azure/active-directory/develop/active-directory-configurable-token-lifetimes#refresh-and-session-token-lifetime-policy-properties),我看不明白)。为确保令牌永不过期,需要在每次运行时更新令牌。Secrets 不支持使用 Action 操作更新,故只能将令牌加密后保存到存储库的 graphtoken 分支,密钥保存在 Secrets 内。
125 |
126 | #### 授权 MyTodoApp 有什么风险?如何取消此授权?
127 |
128 | 正如授权时显示的应用权限所示,“创建、读取、更新和删除你的任务和计划”和“保持对已向 MyTodoApp 授予访问权限的数据的访问权限”是应用正常运行必须的权限,“读取您的个人资料”用于验证登录。程序不会也不可能索要您的敏感信息。在[这里](https://account.live.com/consent/Manage)可以管理连接到 Microsoft 账户的应用。
129 |
130 | #### 无法登录微软账号?该 Microsoft 帐户不存在?
131 |
132 | 换用个人邮箱注册的账号,不要用学校/机构的账号。
133 |
134 | #### GitHub Action 运行失败?
135 |
136 | 如果不明白为什么失败,到原仓库提交 issue
137 |
138 | #### 如果仓库设置为 Public,如何隐藏我的课程信息?
139 |
140 | 编辑`config.yaml`文件,把`VerboseMode`改成`false`
--------------------------------------------------------------------------------
/docs/graph-token-manually.md:
--------------------------------------------------------------------------------
1 | ### 手动配置 Graph Token
2 |
3 | 1. 访问[这个链接](https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=49694ef2-8751-4ac9-8431-8817c27350b4&response_type=code&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&response_mode=query&scope=Tasks.ReadWrite%20User.Read%20offline_access&state=12345),登录 Microsoft 账户
4 |
5 | 
6 |
7 | 2. 授权 MyTodoApp(务必核对权限是否与图中一致)
8 |
9 | 
10 |
11 | 1. 完成后,在地址栏找到 code 参数
12 |
13 | 
14 |
15 | 4. 打开终端(不建议使用 Windows Terminal 和 Powershell)
16 |
17 | 
18 |
19 | 5. 粘贴以下命令并运行(或者使用 Postman 等工具)
20 |
21 | ```bash
22 | curl -d "client_id=49694ef2-8751-4ac9-8431-8817c27350b4&scope=Tasks.ReadWrite%20User.Read%20offline_access&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&grant_type=authorization_code&code=【这里换成你的code!】" https://login.microsoftonline.com/consumers/oauth2/v2.0/token
23 | ```
24 |
25 | 6. 复制“refresh_token”的值
26 |
27 | 
28 |
29 | 7. 打开 [CyberChef-CN](https://1357310795.github.io/CyberChef-CN) 或任何你常用的密码学算法工具(需要能够处理 MD5 哈希和自定义 IV 的 AES 加密)。
30 |
31 | 
32 |
33 | 8. 左侧搜索“MD5”,拖到“配方”里面,并在右上角的输入框输入用于保护凭证的密钥(强烈建议在密钥中包含中文,密钥无需记忆)
34 |
35 | 
36 |
37 | 9. 复制 MD5 的结果,把“配方”清空,左侧搜索“AES”,把“AES Encrypt”拖到“配方”里面,再搜索“Base64”,把“文本转 Base64”拖到“AES Encrypt”的下面
38 |
39 | 
40 |
41 | 10. 在“AES Encrypt”的“Key”和“IV”粘贴刚刚复制的 MD5 值,“Output”改为“Raw”
42 |
43 | 
44 |
45 | 11. 右上角输入框粘贴 (6) 中得到的 refresh_token,复制输出的内容
46 |
47 | 
48 |
49 | 12. 在自己 fork 的仓库,切换到 graphtoken 分支
50 |
51 | 
52 |
53 | 13. 编辑 graphtoken.asc 文件
54 |
55 | 
56 |
57 | 14. 粘贴刚才复制的内容,直接提交
58 |
59 | 
60 |
61 | 15. 再次到“Secrets”里,创建一个新条目,“Name”为“KEY”,“Value”为刚才输入的密钥
62 |
63 | 
--------------------------------------------------------------------------------
/docs/local.md:
--------------------------------------------------------------------------------
1 | ## 配置步骤
2 | ### 一、获取 Canvas 令牌
3 |
4 | 1. 打开 Canvas,进入“设置”页面
5 |
6 | 
7 |
8 | 2. 点击“创建新访问许可证”
9 |
10 | 
11 |
12 | 3. “用途”随便填一个,“过期”建议留空,点击“生成令牌”
13 |
14 | 
15 |
16 | 4. 复制生成的令牌**备用**
17 |
18 | 
19 |
20 | ### 二、获取 Graph 令牌(若要同步到 Microsoft Todo)
21 | #### Windows
22 | 推荐借助“Graph 认证辅助工具”(在本仓库开源)进行配置。
23 |
24 | 5. 在 [Releases](../../../releases) 页面下载“TodoSynchronizer.QuickTool.exe”,打开。
25 |
26 | 6. 点击“获取 Graph 认证”,跳转到浏览器
27 |
28 | 7. 授权 MyTodoApp(务必核对权限是否与图中一致)
29 |
30 | 
31 |
32 | 8. 完成后,回到程序,**点击下面的蓝色的“直接复制 Token”**
33 |
34 | 
35 |
36 | #### Linux
37 | Linux系统可参考[手动配置 Graph Token](./graph-token-manually.md)**(到第六步为止!!!只需要 refresh_token !!!不用加密!!!)**
38 |
39 | ### 三、更新本地 Token 存储文件
40 | 9. 在 [Releases](../../../releases) 界面下载对应系统(Windows/Linux)的本地运行版本程序包。
41 |
42 | 
43 |
44 | 10. 解压程序包到合适的位置(要求程序可读写此位置)
45 |
46 | 11. 使用**文本编辑器**打开程序包目录下的 `token.json` 文件
47 |
48 | 
49 |
50 | 12. 填上前面获取的令牌
51 | - 若要同步到 Microsoft Todo,按以下格式填写
52 | ```
53 | {"CanvasToken":"这里填上你的 Canvas 令牌","GraphToken":"这里填上你的 Graph 令牌"}
54 | ```
55 |
56 | - 若要同步到滴答清单,按以下格式填写
57 | ```
58 | {"CanvasToken":"这里填上你的 Canvas 令牌","DidaCredential":{"phone":"这里填上你的滴答清单登录手机号","password":"这里填上你的滴答清单登录密码"}}
59 | ```
60 |
61 | 
62 |
63 | ### 四、配置定时任务
64 | #### Windows
65 | 13. 搜索“任务计划程序”,打开
66 |
67 | 
68 |
69 | 14. 在“任务计划程序库”或其任一子文件夹内**创建任务**(注意不是“创建基本任务”)
70 |
71 | 
72 |
73 | 15. 按图中配置
74 |
75 | 
76 | 
77 | 
78 |
79 | 其中程序名称为 `wscript.exe`,参数为程序包中 vbs 文件的路径(建议包含引号),例如
80 | - 同步到 Microsoft Todo
81 | ```
82 | "C:\Users\Public\Download\TodoSync.Local\TodoSync-Todo.vbs"
83 | ```
84 | - 同步到 滴答清单
85 | ```
86 | "C:\Users\Public\Download\TodoSync.Local\TodoSync-Dida.vbs"
87 | ```
88 | #### Linux
89 | 下面以 crontab 服务为例展示 Linux 配置定时任务
90 |
91 | 16. 终端运行命令 `crontab -e`,打开文本编辑器
92 |
93 | 17. 光标移动到新的一行,输入
94 | - 同步到 Microsoft Todo
95 | ```
96 | 0 * * * * /path_to_your_program/TodoSyncronizer.CLI -local
97 | ```
98 | - 同步到 滴答清单
99 | ```
100 | 0 * * * * /path_to_your_program/TodoSyncronizer.CLI -local -didacredentialfile 114514
101 | ```
102 |
103 | 
104 |
105 | 18. 按提示保存退出,输入 `crontab -l` 检查是否正确保存
106 |
107 | 
108 |
109 | 19. 定时任务配置完毕,定时任务将在每小时的第零分钟执行。建议第一次时检查程序目录下的 `.log` 文件查看是否成功执行(若没有 `.log` 文件,则可能配置有误)
110 |
--------------------------------------------------------------------------------
/files/todosync-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-512.png
--------------------------------------------------------------------------------
/files/todosync-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-final.png
--------------------------------------------------------------------------------
/files/todosync-head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-head.png
--------------------------------------------------------------------------------
/files/todosync-head.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-head.psd
--------------------------------------------------------------------------------
/files/todosync-main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-main.png
--------------------------------------------------------------------------------
/files/todosync-main.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync-main.sketch
--------------------------------------------------------------------------------
/files/todosync.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1357310795/TodoSync/188c9ba8333753979f4d6f7c8143405e7f743c1d/files/todosync.sketch
--------------------------------------------------------------------------------
/files/user-round.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/files/user.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------