├── .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 | ![](https://s2.loli.net/2022/10/20/2greaNyO7PdvcUh.png) 2 | 3 | ## 程序简介 4 | 基于 GitHub Actions 的定时任务(每小时运行一次同步),将 Canvas LMS 的作业、测验、公告、讨论同步到 Microsoft Todo/滴答清单(目前仅适配上海交通大学 Canvas 系统,理论上所有 Canvas LMS 都能用) 5 | 6 | ![](https://s2.loli.net/2022/09/14/J8WMPXCvjw34ZOq.png) 7 | ![](https://s2.loli.net/2022/10/20/oZjiM92OQtJH1pW.png) 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 |