amki
6 years ago
63 changed files with 13486 additions and 0 deletions
@ -0,0 +1,25 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio Version 16 |
|||
VisualStudioVersion = 16.0.28729.10 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoBot", "DiscoBot\DiscoBot.csproj", "{8667F2ED-08B2-47B6-B7B4-2F78B5FD2AF8}" |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|Any CPU = Debug|Any CPU |
|||
Release|Any CPU = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{8667F2ED-08B2-47B6-B7B4-2F78B5FD2AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{8667F2ED-08B2-47B6-B7B4-2F78B5FD2AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{8667F2ED-08B2-47B6-B7B4-2F78B5FD2AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{8667F2ED-08B2-47B6-B7B4-2F78B5FD2AF8}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
GlobalSection(ExtensibilityGlobals) = postSolution |
|||
SolutionGuid = {60640959-F628-4073-A76A-86BE86EBAF92} |
|||
EndGlobalSection |
|||
EndGlobal |
@ -0,0 +1,231 @@ |
|||
## Ignore Visual Studio temporary files, build results, and |
|||
## files generated by popular Visual Studio add-ons. |
|||
|
|||
# User-specific files |
|||
*.suo |
|||
*.user |
|||
*.userosscache |
|||
*.sln.docstates |
|||
|
|||
# User-specific files (MonoDevelop/Xamarin Studio) |
|||
*.userprefs |
|||
|
|||
# Build results |
|||
[Dd]ebug/ |
|||
[Dd]ebugPublic/ |
|||
[Rr]elease/ |
|||
[Rr]eleases/ |
|||
x64/ |
|||
x86/ |
|||
build/ |
|||
bld/ |
|||
bin/ |
|||
Bin/ |
|||
obj/ |
|||
Obj/ |
|||
|
|||
# Visual Studio 2015 cache/options directory |
|||
.vs/ |
|||
|
|||
# MSTest test Results |
|||
[Tt]est[Rr]esult*/ |
|||
[Bb]uild[Ll]og.* |
|||
|
|||
# NUNIT |
|||
*.VisualState.xml |
|||
TestResult.xml |
|||
|
|||
# Build Results of an ATL Project |
|||
[Dd]ebugPS/ |
|||
[Rr]eleasePS/ |
|||
dlldata.c |
|||
|
|||
*_i.c |
|||
*_p.c |
|||
*_i.h |
|||
*.ilk |
|||
*.meta |
|||
*.obj |
|||
*.pch |
|||
*.pdb |
|||
*.pgc |
|||
*.pgd |
|||
*.rsp |
|||
*.sbr |
|||
*.tlb |
|||
*.tli |
|||
*.tlh |
|||
*.tmp |
|||
*.tmp_proj |
|||
*.log |
|||
*.vspscc |
|||
*.vssscc |
|||
.builds |
|||
*.pidb |
|||
*.svclog |
|||
*.scc |
|||
|
|||
# Chutzpah Test files |
|||
_Chutzpah* |
|||
|
|||
# Visual C++ cache files |
|||
ipch/ |
|||
*.aps |
|||
*.ncb |
|||
*.opendb |
|||
*.opensdf |
|||
*.sdf |
|||
*.cachefile |
|||
|
|||
# Visual Studio profiler |
|||
*.psess |
|||
*.vsp |
|||
*.vspx |
|||
*.sap |
|||
|
|||
# TFS 2012 Local Workspace |
|||
$tf/ |
|||
|
|||
# Guidance Automation Toolkit |
|||
*.gpState |
|||
|
|||
# ReSharper is a .NET coding add-in |
|||
_ReSharper*/ |
|||
*.[Rr]e[Ss]harper |
|||
*.DotSettings.user |
|||
|
|||
# JustCode is a .NET coding add-in |
|||
.JustCode |
|||
|
|||
# TeamCity is a build add-in |
|||
_TeamCity* |
|||
|
|||
# DotCover is a Code Coverage Tool |
|||
*.dotCover |
|||
|
|||
# NCrunch |
|||
_NCrunch_* |
|||
.*crunch*.local.xml |
|||
nCrunchTemp_* |
|||
|
|||
# MightyMoose |
|||
*.mm.* |
|||
AutoTest.Net/ |
|||
|
|||
# Web workbench (sass) |
|||
.sass-cache/ |
|||
|
|||
# Installshield output folder |
|||
[Ee]xpress/ |
|||
|
|||
# DocProject is a documentation generator add-in |
|||
DocProject/buildhelp/ |
|||
DocProject/Help/*.HxT |
|||
DocProject/Help/*.HxC |
|||
DocProject/Help/*.hhc |
|||
DocProject/Help/*.hhk |
|||
DocProject/Help/*.hhp |
|||
DocProject/Help/Html2 |
|||
DocProject/Help/html |
|||
|
|||
# Click-Once directory |
|||
publish/ |
|||
|
|||
# Publish Web Output |
|||
*.[Pp]ublish.xml |
|||
*.azurePubxml |
|||
# TODO: Comment the next line if you want to checkin your web deploy settings |
|||
# but database connection strings (with potential passwords) will be unencrypted |
|||
*.pubxml |
|||
*.publishproj |
|||
|
|||
# NuGet Packages |
|||
*.nupkg |
|||
# The packages folder can be ignored because of Package Restore |
|||
**/packages/* |
|||
# except build/, which is used as an MSBuild target. |
|||
!**/packages/build/ |
|||
# Uncomment if necessary however generally it will be regenerated when needed |
|||
#!**/packages/repositories.config |
|||
|
|||
# Microsoft Azure Build Output |
|||
csx/ |
|||
*.build.csdef |
|||
|
|||
# Microsoft Azure Emulator |
|||
ecf/ |
|||
rcf/ |
|||
|
|||
# Microsoft Azure ApplicationInsights config file |
|||
ApplicationInsights.config |
|||
|
|||
# Windows Store app package directory |
|||
AppPackages/ |
|||
BundleArtifacts/ |
|||
|
|||
# Visual Studio cache files |
|||
# files ending in .cache can be ignored |
|||
*.[Cc]ache |
|||
# but keep track of directories ending in .cache |
|||
!*.[Cc]ache/ |
|||
|
|||
# Others |
|||
ClientBin/ |
|||
~$* |
|||
*~ |
|||
*.dbmdl |
|||
*.dbproj.schemaview |
|||
*.pfx |
|||
*.publishsettings |
|||
orleans.codegen.cs |
|||
|
|||
/node_modules |
|||
|
|||
# RIA/Silverlight projects |
|||
Generated_Code/ |
|||
|
|||
# Backup & report files from converting an old project file |
|||
# to a newer Visual Studio version. Backup files are not needed, |
|||
# because we have git ;-) |
|||
_UpgradeReport_Files/ |
|||
Backup*/ |
|||
UpgradeLog*.XML |
|||
UpgradeLog*.htm |
|||
|
|||
# SQL Server files |
|||
*.mdf |
|||
*.ldf |
|||
|
|||
# Business Intelligence projects |
|||
*.rdl.data |
|||
*.bim.layout |
|||
*.bim_*.settings |
|||
|
|||
# Microsoft Fakes |
|||
FakesAssemblies/ |
|||
|
|||
# GhostDoc plugin setting file |
|||
*.GhostDoc.xml |
|||
|
|||
# Node.js Tools for Visual Studio |
|||
.ntvs_analysis.dat |
|||
|
|||
# Visual Studio 6 build log |
|||
*.plg |
|||
|
|||
# Visual Studio 6 workspace options file |
|||
*.opt |
|||
|
|||
# Visual Studio LightSwitch build output |
|||
**/*.HTMLClient/GeneratedArtifacts |
|||
**/*.DesktopClient/GeneratedArtifacts |
|||
**/*.DesktopClient/ModelManifest.xml |
|||
**/*.Server/GeneratedArtifacts |
|||
**/*.Server/ModelManifest.xml |
|||
_Pvt_Extensions |
|||
|
|||
# Paket dependency manager |
|||
.paket/paket.exe |
|||
|
|||
# FAKE - F# Make |
|||
.fake/ |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@ |
|||
# Editor configuration, see http://editorconfig.org |
|||
root = true |
|||
|
|||
[*] |
|||
charset = utf-8 |
|||
indent_style = space |
|||
indent_size = 2 |
|||
insert_final_newline = true |
|||
trim_trailing_whitespace = true |
|||
|
|||
[*.md] |
|||
max_line_length = off |
|||
trim_trailing_whitespace = false |
@ -0,0 +1,40 @@ |
|||
# See http://help.github.com/ignore-files/ for more about ignoring files. |
|||
|
|||
# compiled output |
|||
/dist |
|||
/dist-server |
|||
/tmp |
|||
/out-tsc |
|||
|
|||
# dependencies |
|||
/node_modules |
|||
|
|||
# IDEs and editors |
|||
/.idea |
|||
.project |
|||
.classpath |
|||
.c9/ |
|||
*.launch |
|||
.settings/ |
|||
*.sublime-workspace |
|||
|
|||
# IDE - VSCode |
|||
.vscode/* |
|||
!.vscode/settings.json |
|||
!.vscode/tasks.json |
|||
!.vscode/launch.json |
|||
!.vscode/extensions.json |
|||
|
|||
# misc |
|||
/.sass-cache |
|||
/connect.lock |
|||
/coverage |
|||
/libpeerconnection.log |
|||
npm-debug.log |
|||
yarn-error.log |
|||
testem.log |
|||
/typings |
|||
|
|||
# System Files |
|||
.DS_Store |
|||
Thumbs.db |
@ -0,0 +1,27 @@ |
|||
# DiscoBot |
|||
|
|||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0. |
|||
|
|||
## Development server |
|||
|
|||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. |
|||
|
|||
## Code scaffolding |
|||
|
|||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. |
|||
|
|||
## Build |
|||
|
|||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. |
|||
|
|||
## Running unit tests |
|||
|
|||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). |
|||
|
|||
## Running end-to-end tests |
|||
|
|||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). |
|||
|
|||
## Further help |
|||
|
|||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). |
@ -0,0 +1,149 @@ |
|||
{ |
|||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
|||
"version": 1, |
|||
"newProjectRoot": "projects", |
|||
"projects": { |
|||
"DiscoBot": { |
|||
"root": "", |
|||
"sourceRoot": "src", |
|||
"projectType": "application", |
|||
"prefix": "app", |
|||
"schematics": {}, |
|||
"architect": { |
|||
"build": { |
|||
"builder": "@angular-devkit/build-angular:browser", |
|||
"options": { |
|||
"progress": true, |
|||
"extractCss": true, |
|||
"outputPath": "dist", |
|||
"index": "src/index.html", |
|||
"main": "src/main.ts", |
|||
"polyfills": "src/polyfills.ts", |
|||
"tsConfig": "src/tsconfig.app.json", |
|||
"assets": [ |
|||
"src/assets" |
|||
], |
|||
"styles": [ |
|||
"node_modules/bootstrap/dist/css/bootstrap.min.css", |
|||
"src/styles.css" |
|||
], |
|||
"scripts": [] |
|||
}, |
|||
"configurations": { |
|||
"production": { |
|||
"fileReplacements": [ |
|||
{ |
|||
"replace": "src/environments/environment.ts", |
|||
"with": "src/environments/environment.prod.ts" |
|||
} |
|||
], |
|||
"optimization": true, |
|||
"outputHashing": "all", |
|||
"sourceMap": false, |
|||
"extractCss": true, |
|||
"namedChunks": false, |
|||
"aot": true, |
|||
"extractLicenses": true, |
|||
"vendorChunk": false, |
|||
"buildOptimizer": true |
|||
} |
|||
} |
|||
}, |
|||
"serve": { |
|||
"builder": "@angular-devkit/build-angular:dev-server", |
|||
"options": { |
|||
"browserTarget": "DiscoBot:build" |
|||
}, |
|||
"configurations": { |
|||
"production": { |
|||
"browserTarget": "DiscoBot:build:production" |
|||
} |
|||
} |
|||
}, |
|||
"extract-i18n": { |
|||
"builder": "@angular-devkit/build-angular:extract-i18n", |
|||
"options": { |
|||
"browserTarget": "DiscoBot:build" |
|||
} |
|||
}, |
|||
"test": { |
|||
"builder": "@angular-devkit/build-angular:karma", |
|||
"options": { |
|||
"main": "src/test.ts", |
|||
"polyfills": "src/polyfills.ts", |
|||
"tsConfig": "src/tsconfig.spec.json", |
|||
"karmaConfig": "src/karma.conf.js", |
|||
"styles": [ |
|||
"styles.css" |
|||
], |
|||
"scripts": [], |
|||
"assets": [ |
|||
"src/assets" |
|||
] |
|||
} |
|||
}, |
|||
"lint": { |
|||
"builder": "@angular-devkit/build-angular:tslint", |
|||
"options": { |
|||
"tsConfig": [ |
|||
"src/tsconfig.app.json", |
|||
"src/tsconfig.spec.json" |
|||
], |
|||
"exclude": [ |
|||
"**/node_modules/**" |
|||
] |
|||
} |
|||
}, |
|||
"server": { |
|||
"builder": "@angular-devkit/build-angular:server", |
|||
"options": { |
|||
"outputPath": "dist-server", |
|||
"main": "src/main.ts", |
|||
"tsConfig": "src/tsconfig.server.json" |
|||
}, |
|||
"configurations": { |
|||
"dev": { |
|||
"optimization": true, |
|||
"outputHashing": "all", |
|||
"sourceMap": false, |
|||
"namedChunks": false, |
|||
"extractLicenses": true, |
|||
"vendorChunk": true |
|||
}, |
|||
"production": { |
|||
"optimization": true, |
|||
"outputHashing": "all", |
|||
"sourceMap": false, |
|||
"namedChunks": false, |
|||
"extractLicenses": true, |
|||
"vendorChunk": false |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"DiscoBot-e2e": { |
|||
"root": "e2e/", |
|||
"projectType": "application", |
|||
"architect": { |
|||
"e2e": { |
|||
"builder": "@angular-devkit/build-angular:protractor", |
|||
"options": { |
|||
"protractorConfig": "e2e/protractor.conf.js", |
|||
"devServerTarget": "DiscoBot:serve" |
|||
} |
|||
}, |
|||
"lint": { |
|||
"builder": "@angular-devkit/build-angular:tslint", |
|||
"options": { |
|||
"tsConfig": "e2e/tsconfig.e2e.json", |
|||
"exclude": [ |
|||
"**/node_modules/**" |
|||
] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"defaultProject": "DiscoBot" |
|||
} |
@ -0,0 +1,28 @@ |
|||
// Protractor configuration file, see link for more information
|
|||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|||
|
|||
const { SpecReporter } = require('jasmine-spec-reporter'); |
|||
|
|||
exports.config = { |
|||
allScriptsTimeout: 11000, |
|||
specs: [ |
|||
'./src/**/*.e2e-spec.ts' |
|||
], |
|||
capabilities: { |
|||
'browserName': 'chrome' |
|||
}, |
|||
directConnect: true, |
|||
baseUrl: 'http://localhost:4200/', |
|||
framework: 'jasmine', |
|||
jasmineNodeOpts: { |
|||
showColors: true, |
|||
defaultTimeoutInterval: 30000, |
|||
print: function() {} |
|||
}, |
|||
onPrepare() { |
|||
require('ts-node').register({ |
|||
project: require('path').join(__dirname, './tsconfig.e2e.json') |
|||
}); |
|||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); |
|||
} |
|||
}; |
@ -0,0 +1,14 @@ |
|||
import { AppPage } from './app.po'; |
|||
|
|||
describe('App', () => { |
|||
let page: AppPage; |
|||
|
|||
beforeEach(() => { |
|||
page = new AppPage(); |
|||
}); |
|||
|
|||
it('should display welcome message', () => { |
|||
page.navigateTo(); |
|||
expect(page.getMainHeading()).toEqual('Hello, world!'); |
|||
}); |
|||
}); |
@ -0,0 +1,11 @@ |
|||
import { browser, by, element } from 'protractor'; |
|||
|
|||
export class AppPage { |
|||
navigateTo() { |
|||
return browser.get('/'); |
|||
} |
|||
|
|||
getMainHeading() { |
|||
return element(by.css('app-root h1')).getText(); |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"extends": "../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../out-tsc/app", |
|||
"module": "commonjs", |
|||
"target": "es5", |
|||
"types": [ |
|||
"jasmine", |
|||
"jasminewd2", |
|||
"node" |
|||
] |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,59 @@ |
|||
{ |
|||
"name": "DiscoBot", |
|||
"version": "0.0.0", |
|||
"scripts": { |
|||
"ng": "ng", |
|||
"start": "ng serve", |
|||
"build": "ng build", |
|||
"build:ssr": "ng run DiscoBot:server:dev", |
|||
"test": "ng test", |
|||
"lint": "ng lint", |
|||
"e2e": "ng e2e" |
|||
}, |
|||
"private": true, |
|||
"dependencies": { |
|||
"@angular/animations": "7.2.5", |
|||
"@angular/common": "7.2.5", |
|||
"@angular/compiler": "7.2.5", |
|||
"@angular/core": "7.2.5", |
|||
"@angular/forms": "7.2.5", |
|||
"@angular/http": "7.2.5", |
|||
"@angular/platform-browser": "7.2.5", |
|||
"@angular/platform-browser-dynamic": "7.2.5", |
|||
"@angular/platform-server": "7.2.5", |
|||
"@angular/router": "7.2.5", |
|||
"@nguniversal/module-map-ngfactory-loader": "7.1.0", |
|||
"core-js": "^2.6.5", |
|||
"rxjs": "^6.4.0", |
|||
"zone.js": "^0.8.29", |
|||
"aspnet-prerendering": "^3.0.1", |
|||
"bootstrap": "^4.3.1", |
|||
"jquery": "3.3.1", |
|||
"oidc-client": "^1.6.1", |
|||
"popper.js": "^1.14.3" |
|||
}, |
|||
"devDependencies": { |
|||
"@angular-devkit/build-angular": "~0.13.2", |
|||
"@angular/cli": "~7.3.2", |
|||
"@angular/compiler-cli": "7.2.5", |
|||
"@angular/language-service": "^7.2.5", |
|||
"@types/jasmine": "~3.3.9", |
|||
"@types/jasminewd2": "~2.0.6", |
|||
"@types/node": "~11.9.4", |
|||
"codelyzer": "~4.5.0", |
|||
"jasmine-core": "~3.3.0", |
|||
"jasmine-spec-reporter": "~4.2.1", |
|||
"karma": "^4.0.0", |
|||
"karma-chrome-launcher": "~2.2.0", |
|||
"karma-coverage-istanbul-reporter": "~2.0.5", |
|||
"karma-jasmine": "~2.0.1", |
|||
"karma-jasmine-html-reporter": "^1.4.0", |
|||
"typescript": "~3.2.4" |
|||
}, |
|||
"optionalDependencies": { |
|||
"node-sass": "^4.9.3", |
|||
"protractor": "~5.4.0", |
|||
"ts-node": "~5.0.1", |
|||
"tslint": "~5.9.1" |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<body> |
|||
<app-nav-menu></app-nav-menu> |
|||
<div class="container"> |
|||
<router-outlet></router-outlet> |
|||
</div> |
|||
</body> |
@ -0,0 +1,9 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-root', |
|||
templateUrl: './app.component.html' |
|||
}) |
|||
export class AppComponent { |
|||
title = 'app'; |
|||
} |
@ -0,0 +1,34 @@ |
|||
import { BrowserModule } from '@angular/platform-browser'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { FormsModule } from '@angular/forms'; |
|||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; |
|||
import { RouterModule } from '@angular/router'; |
|||
|
|||
import { AppComponent } from './app.component'; |
|||
import { NavMenuComponent } from './nav-menu/nav-menu.component'; |
|||
import { HomeComponent } from './home/home.component'; |
|||
import { CounterComponent } from './counter/counter.component'; |
|||
import { FetchDataComponent } from './fetch-data/fetch-data.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
AppComponent, |
|||
NavMenuComponent, |
|||
HomeComponent, |
|||
CounterComponent, |
|||
FetchDataComponent |
|||
], |
|||
imports: [ |
|||
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), |
|||
HttpClientModule, |
|||
FormsModule, |
|||
RouterModule.forRoot([ |
|||
{ path: '', component: HomeComponent, pathMatch: 'full' }, |
|||
{ path: 'counter', component: CounterComponent }, |
|||
{ path: 'fetch-data', component: FetchDataComponent }, |
|||
]) |
|||
], |
|||
providers: [], |
|||
bootstrap: [AppComponent] |
|||
}) |
|||
export class AppModule { } |
@ -0,0 +1,11 @@ |
|||
import { NgModule } from '@angular/core'; |
|||
import { ServerModule } from '@angular/platform-server'; |
|||
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; |
|||
import { AppComponent } from './app.component'; |
|||
import { AppModule } from './app.module'; |
|||
|
|||
@NgModule({ |
|||
imports: [AppModule, ServerModule, ModuleMapLoaderModule], |
|||
bootstrap: [AppComponent] |
|||
}) |
|||
export class AppServerModule { } |
@ -0,0 +1,7 @@ |
|||
<h1>Counter</h1> |
|||
|
|||
<p>This is a simple example of an Angular component.</p> |
|||
|
|||
<p>Current count: <strong>{{ currentCount }}</strong></p> |
|||
|
|||
<button class="btn btn-primary" (click)="incrementCounter()">Increment</button> |
@ -0,0 +1,36 @@ |
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
|||
|
|||
import { CounterComponent } from './counter.component'; |
|||
|
|||
describe('CounterComponent', () => { |
|||
let component: CounterComponent; |
|||
let fixture: ComponentFixture<CounterComponent>; |
|||
|
|||
beforeEach(async(() => { |
|||
TestBed.configureTestingModule({ |
|||
declarations: [ CounterComponent ] |
|||
}) |
|||
.compileComponents(); |
|||
})); |
|||
|
|||
beforeEach(() => { |
|||
fixture = TestBed.createComponent(CounterComponent); |
|||
component = fixture.componentInstance; |
|||
fixture.detectChanges(); |
|||
}); |
|||
|
|||
it('should display a title', async(() => { |
|||
const titleText = fixture.nativeElement.querySelector('h1').textContent; |
|||
expect(titleText).toEqual('Counter'); |
|||
})); |
|||
|
|||
it('should start with count 0, then increments by 1 when clicked', async(() => { |
|||
const countElement = fixture.nativeElement.querySelector('strong'); |
|||
expect(countElement.textContent).toEqual('0'); |
|||
|
|||
const incrementButton = fixture.nativeElement.querySelector('button'); |
|||
incrementButton.click(); |
|||
fixture.detectChanges(); |
|||
expect(countElement.textContent).toEqual('1'); |
|||
})); |
|||
}); |
@ -0,0 +1,13 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-counter-component', |
|||
templateUrl: './counter.component.html' |
|||
}) |
|||
export class CounterComponent { |
|||
public currentCount = 0; |
|||
|
|||
public incrementCounter() { |
|||
this.currentCount = this.currentCount+4; |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<h1>Weather forecast</h1> |
|||
|
|||
<p>This component demonstrates fetching data from the server.</p> |
|||
|
|||
<p *ngIf="!forecasts"><em>Loading...</em></p> |
|||
|
|||
<table class='table table-striped' *ngIf="forecasts"> |
|||
<thead> |
|||
<tr> |
|||
<th>Date</th> |
|||
<th>Temp. (C)</th> |
|||
<th>Temp. (F)</th> |
|||
<th>Summary</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr *ngFor="let forecast of forecasts"> |
|||
<td>{{ forecast.dateFormatted }}</td> |
|||
<td>{{ forecast.temperatureC }}</td> |
|||
<td>{{ forecast.temperatureF }}</td> |
|||
<td>{{ forecast.summary }}</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
@ -0,0 +1,23 @@ |
|||
import { Component, Inject } from '@angular/core'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
|
|||
@Component({ |
|||
selector: 'app-fetch-data', |
|||
templateUrl: './fetch-data.component.html' |
|||
}) |
|||
export class FetchDataComponent { |
|||
public forecasts: WeatherForecast[]; |
|||
|
|||
constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) { |
|||
http.get<WeatherForecast[]>(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => { |
|||
this.forecasts = result; |
|||
}, error => console.error(error)); |
|||
} |
|||
} |
|||
|
|||
interface WeatherForecast { |
|||
dateFormatted: string; |
|||
temperatureC: number; |
|||
temperatureF: number; |
|||
summary: string; |
|||
} |
@ -0,0 +1,14 @@ |
|||
<h1>Hello, world!</h1> |
|||
<p>Welcome to your new single-page application, built with:</p> |
|||
<ul> |
|||
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li> |
|||
<li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li> |
|||
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li> |
|||
</ul> |
|||
<p>To help you get started, we've also set up:</p> |
|||
<ul> |
|||
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li> |
|||
<li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li> |
|||
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li> |
|||
</ul> |
|||
<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p> |
@ -0,0 +1,8 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-home', |
|||
templateUrl: './home.component.html', |
|||
}) |
|||
export class HomeComponent { |
|||
} |
@ -0,0 +1,18 @@ |
|||
a.navbar-brand { |
|||
white-space: normal; |
|||
text-align: center; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
html { |
|||
font-size: 14px; |
|||
} |
|||
@media (min-width: 768px) { |
|||
html { |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
|
|||
.box-shadow { |
|||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); |
|||
} |
@ -0,0 +1,24 @@ |
|||
<header> |
|||
<nav class='navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'> |
|||
<div class="container"> |
|||
<a class="navbar-brand" [routerLink]='["/"]'>DiscoBot</a> |
|||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-label="Toggle navigation" |
|||
[attr.aria-expanded]="isExpanded" (click)="toggle()"> |
|||
<span class="navbar-toggler-icon"></span> |
|||
</button> |
|||
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='{"show": isExpanded}'> |
|||
<ul class="navbar-nav flex-grow"> |
|||
<li class="nav-item" [routerLinkActive]='["link-active"]' [routerLinkActiveOptions]='{ exact: true }'> |
|||
<a class="nav-link text-dark" [routerLink]='["/"]'>Home</a> |
|||
</li> |
|||
<li class="nav-item" [routerLinkActive]='["link-active"]'> |
|||
<a class="nav-link text-dark" [routerLink]='["/counter"]'>Counter</a> |
|||
</li> |
|||
<li class="nav-item" [routerLinkActive]='["link-active"]'> |
|||
<a class="nav-link text-dark" [routerLink]='["/fetch-data"]'>Fetch data</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
</header> |
@ -0,0 +1,18 @@ |
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-nav-menu', |
|||
templateUrl: './nav-menu.component.html', |
|||
styleUrls: ['./nav-menu.component.css'] |
|||
}) |
|||
export class NavMenuComponent { |
|||
isExpanded = false; |
|||
|
|||
collapse() { |
|||
this.isExpanded = false; |
|||
} |
|||
|
|||
toggle() { |
|||
this.isExpanded = !this.isExpanded; |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers |
|||
# For additional information regarding the format and rule options, please see: |
|||
# https://github.com/browserslist/browserslist#queries |
|||
# For IE 9-11 support, please uncomment the last line of the file and adjust as needed |
|||
> 0.5% |
|||
last 2 versions |
|||
Firefox ESR |
|||
not dead |
|||
# IE 9-11 |
@ -0,0 +1,3 @@ |
|||
export const environment = { |
|||
production: true |
|||
}; |
@ -0,0 +1,15 @@ |
|||
// This file can be replaced during build by using the `fileReplacements` array.
|
|||
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
|
|||
// The list of file replacements can be found in `angular.json`.
|
|||
|
|||
export const environment = { |
|||
production: false |
|||
}; |
|||
|
|||
/* |
|||
* In development mode, to ignore zone related error stack frames such as |
|||
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can |
|||
* import the following file, but please comment it out in production mode |
|||
* because it will have performance impact when throw error |
|||
*/ |
|||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
@ -0,0 +1,14 @@ |
|||
<!doctype html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>DiscoBot</title> |
|||
<base href="/"> |
|||
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<link rel="icon" type="image/x-icon" href="favicon.ico"> |
|||
</head> |
|||
<body> |
|||
<app-root>Loading...</app-root> |
|||
</body> |
|||
</html> |
@ -0,0 +1,31 @@ |
|||
// Karma configuration file, see link for more information
|
|||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|||
|
|||
module.exports = function (config) { |
|||
config.set({ |
|||
basePath: '', |
|||
frameworks: ['jasmine', '@angular-devkit/build-angular'], |
|||
plugins: [ |
|||
require('karma-jasmine'), |
|||
require('karma-chrome-launcher'), |
|||
require('karma-jasmine-html-reporter'), |
|||
require('karma-coverage-istanbul-reporter'), |
|||
require('@angular-devkit/build-angular/plugins/karma') |
|||
], |
|||
client: { |
|||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|||
}, |
|||
coverageIstanbulReporter: { |
|||
dir: require('path').join(__dirname, '../coverage'), |
|||
reports: ['html', 'lcovonly'], |
|||
fixWebpackSourcePaths: true |
|||
}, |
|||
reporters: ['progress', 'kjhtml'], |
|||
port: 9876, |
|||
colors: true, |
|||
logLevel: config.LOG_INFO, |
|||
autoWatch: true, |
|||
browsers: ['Chrome'], |
|||
singleRun: false |
|||
}); |
|||
}; |
@ -0,0 +1,20 @@ |
|||
import { enableProdMode } from '@angular/core'; |
|||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; |
|||
|
|||
import { AppModule } from './app/app.module'; |
|||
import { environment } from './environments/environment'; |
|||
|
|||
export function getBaseUrl() { |
|||
return document.getElementsByTagName('base')[0].href; |
|||
} |
|||
|
|||
const providers = [ |
|||
{ provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } |
|||
]; |
|||
|
|||
if (environment.production) { |
|||
enableProdMode(); |
|||
} |
|||
|
|||
platformBrowserDynamic(providers).bootstrapModule(AppModule) |
|||
.catch(err => console.log(err)); |
@ -0,0 +1,80 @@ |
|||
/** |
|||
* This file includes polyfills needed by Angular and is loaded before the app. |
|||
* You can add your own extra polyfills to this file. |
|||
* |
|||
* This file is divided into 2 sections: |
|||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. |
|||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main |
|||
* file. |
|||
* |
|||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that |
|||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), |
|||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. |
|||
* |
|||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
|||
*/ |
|||
|
|||
/*************************************************************************************************** |
|||
* BROWSER POLYFILLS |
|||
*/ |
|||
|
|||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/ |
|||
// import 'core-js/es6/symbol';
|
|||
// import 'core-js/es6/object';
|
|||
// import 'core-js/es6/function';
|
|||
// import 'core-js/es6/parse-int';
|
|||
// import 'core-js/es6/parse-float';
|
|||
// import 'core-js/es6/number';
|
|||
// import 'core-js/es6/math';
|
|||
// import 'core-js/es6/string';
|
|||
// import 'core-js/es6/date';
|
|||
// import 'core-js/es6/array';
|
|||
// import 'core-js/es6/regexp';
|
|||
// import 'core-js/es6/map';
|
|||
// import 'core-js/es6/weak-map';
|
|||
// import 'core-js/es6/set';
|
|||
|
|||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */ |
|||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
|||
|
|||
/** IE10 and IE11 requires the following for the Reflect API. */ |
|||
// import 'core-js/es6/reflect';
|
|||
|
|||
|
|||
/** Evergreen browsers require these. **/ |
|||
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
|
|||
import 'core-js/es7/reflect'; |
|||
|
|||
|
|||
/** |
|||
* Web Animations `@angular/platform-browser/animations` |
|||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. |
|||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). |
|||
**/ |
|||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
|||
|
|||
/** |
|||
* By default, zone.js will patch all possible macroTask and DomEvents |
|||
* user can disable parts of macroTask/DomEvents patch by setting following flags |
|||
*/ |
|||
|
|||
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
|||
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
|||
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
|||
|
|||
/* |
|||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js |
|||
* with the following flag, it will bypass `zone.js` patch for IE/Edge |
|||
*/ |
|||
// (window as any).__Zone_enable_cross_context_check = true;
|
|||
|
|||
/*************************************************************************************************** |
|||
* Zone JS is required by default for Angular itself. |
|||
*/ |
|||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||
|
|||
|
|||
|
|||
/*************************************************************************************************** |
|||
* APPLICATION IMPORTS |
|||
*/ |
@ -0,0 +1 @@ |
|||
/* You can add global styles to this file, and also import other style files */ |
@ -0,0 +1,20 @@ |
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|||
|
|||
import 'zone.js/dist/zone-testing'; |
|||
import { getTestBed } from '@angular/core/testing'; |
|||
import { |
|||
BrowserDynamicTestingModule, |
|||
platformBrowserDynamicTesting |
|||
} from '@angular/platform-browser-dynamic/testing'; |
|||
|
|||
declare const require: any; |
|||
|
|||
// First, initialize the Angular testing environment.
|
|||
getTestBed().initTestEnvironment( |
|||
BrowserDynamicTestingModule, |
|||
platformBrowserDynamicTesting() |
|||
); |
|||
// Then we find all the tests.
|
|||
const context = require.context('./', true, /\.spec\.ts$/); |
|||
// And load the modules.
|
|||
context.keys().map(context); |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"extends": "../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../out-tsc/app", |
|||
"module": "es2015", |
|||
"types": [] |
|||
}, |
|||
"exclude": [ |
|||
"src/test.ts", |
|||
"**/*.spec.ts" |
|||
] |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"extends": "../tsconfig.json", |
|||
"compilerOptions": { |
|||
"module": "commonjs" |
|||
}, |
|||
"angularCompilerOptions": { |
|||
"entryModule": "app/app.server.module#AppServerModule" |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"extends": "../tsconfig.json", |
|||
"compilerOptions": { |
|||
"outDir": "../out-tsc/spec", |
|||
"module": "commonjs", |
|||
"types": [ |
|||
"jasmine", |
|||
"node" |
|||
] |
|||
}, |
|||
"files": [ |
|||
"test.ts", |
|||
"polyfills.ts" |
|||
], |
|||
"include": [ |
|||
"**/*.spec.ts", |
|||
"**/*.d.ts" |
|||
] |
|||
} |
@ -0,0 +1,17 @@ |
|||
{ |
|||
"extends": "../tslint.json", |
|||
"rules": { |
|||
"directive-selector": [ |
|||
true, |
|||
"attribute", |
|||
"app", |
|||
"camelCase" |
|||
], |
|||
"component-selector": [ |
|||
true, |
|||
"element", |
|||
"app", |
|||
"kebab-case" |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
{ |
|||
"compileOnSave": false, |
|||
"compilerOptions": { |
|||
"baseUrl": "./", |
|||
"outDir": "./dist/out-tsc", |
|||
"sourceMap": true, |
|||
"declaration": false, |
|||
"moduleResolution": "node", |
|||
"emitDecoratorMetadata": true, |
|||
"experimentalDecorators": true, |
|||
"target": "es5", |
|||
"typeRoots": [ |
|||
"node_modules/@types" |
|||
], |
|||
"lib": [ |
|||
"es2017", |
|||
"dom" |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,130 @@ |
|||
{ |
|||
"rulesDirectory": [ |
|||
"node_modules/codelyzer" |
|||
], |
|||
"rules": { |
|||
"arrow-return-shorthand": true, |
|||
"callable-types": true, |
|||
"class-name": true, |
|||
"comment-format": [ |
|||
true, |
|||
"check-space" |
|||
], |
|||
"curly": true, |
|||
"deprecation": { |
|||
"severity": "warn" |
|||
}, |
|||
"eofline": true, |
|||
"forin": true, |
|||
"import-blacklist": [ |
|||
true, |
|||
"rxjs/Rx" |
|||
], |
|||
"import-spacing": true, |
|||
"indent": [ |
|||
true, |
|||
"spaces" |
|||
], |
|||
"interface-over-type-literal": true, |
|||
"label-position": true, |
|||
"max-line-length": [ |
|||
true, |
|||
140 |
|||
], |
|||
"member-access": false, |
|||
"member-ordering": [ |
|||
true, |
|||
{ |
|||
"order": [ |
|||
"static-field", |
|||
"instance-field", |
|||
"static-method", |
|||
"instance-method" |
|||
] |
|||
} |
|||
], |
|||
"no-arg": true, |
|||
"no-bitwise": true, |
|||
"no-console": [ |
|||
true, |
|||
"debug", |
|||
"info", |
|||
"time", |
|||
"timeEnd", |
|||
"trace" |
|||
], |
|||
"no-construct": true, |
|||
"no-debugger": true, |
|||
"no-duplicate-super": true, |
|||
"no-empty": false, |
|||
"no-empty-interface": true, |
|||
"no-eval": true, |
|||
"no-inferrable-types": [ |
|||
true, |
|||
"ignore-params" |
|||
], |
|||
"no-misused-new": true, |
|||
"no-non-null-assertion": true, |
|||
"no-shadowed-variable": true, |
|||
"no-string-literal": false, |
|||
"no-string-throw": true, |
|||
"no-switch-case-fall-through": true, |
|||
"no-trailing-whitespace": true, |
|||
"no-unnecessary-initializer": true, |
|||
"no-unused-expression": true, |
|||
"no-use-before-declare": true, |
|||
"no-var-keyword": true, |
|||
"object-literal-sort-keys": false, |
|||
"one-line": [ |
|||
true, |
|||
"check-open-brace", |
|||
"check-catch", |
|||
"check-else", |
|||
"check-whitespace" |
|||
], |
|||
"prefer-const": true, |
|||
"quotemark": [ |
|||
true, |
|||
"single" |
|||
], |
|||
"radix": true, |
|||
"semicolon": [ |
|||
true, |
|||
"always" |
|||
], |
|||
"triple-equals": [ |
|||
true, |
|||
"allow-null-check" |
|||
], |
|||
"typedef-whitespace": [ |
|||
true, |
|||
{ |
|||
"call-signature": "nospace", |
|||
"index-signature": "nospace", |
|||
"parameter": "nospace", |
|||
"property-declaration": "nospace", |
|||
"variable-declaration": "nospace" |
|||
} |
|||
], |
|||
"unified-signatures": true, |
|||
"variable-name": false, |
|||
"whitespace": [ |
|||
true, |
|||
"check-branch", |
|||
"check-decl", |
|||
"check-operator", |
|||
"check-separator", |
|||
"check-type" |
|||
], |
|||
"no-output-on-prefix": true, |
|||
"use-input-property-decorator": true, |
|||
"use-output-property-decorator": true, |
|||
"use-host-property-decorator": true, |
|||
"no-input-rename": true, |
|||
"no-output-rename": true, |
|||
"use-life-cycle-interface": true, |
|||
"use-pipe-transform-interface": true, |
|||
"component-class-suffix": true, |
|||
"directive-class-suffix": true |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace DiscoBot.Controllers |
|||
{ |
|||
[Route("api/[controller]")]
|
|||
public class SampleDataController : Controller |
|||
{ |
|||
private static string[] Summaries = new[] |
|||
{ |
|||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" |
|||
}; |
|||
|
|||
[HttpGet("[action]")]
|
|||
public IEnumerable<WeatherForecast> WeatherForecasts() |
|||
{ |
|||
var rng = new Random(); |
|||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast |
|||
{ |
|||
DateFormatted = DateTime.Now.AddDays(index).ToString("d"), |
|||
TemperatureC = rng.Next(-20, 55), |
|||
Summary = Summaries[rng.Next(Summaries.Length)] |
|||
}); |
|||
} |
|||
|
|||
public class WeatherForecast |
|||
{ |
|||
public string DateFormatted { get; set; } |
|||
public int TemperatureC { get; set; } |
|||
public string Summary { get; set; } |
|||
|
|||
public int TemperatureF |
|||
{ |
|||
get |
|||
{ |
|||
return 32 + (int)(TemperatureC / 0.5556); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,174 @@ |
|||
using Discord; |
|||
using Discord.WebSocket; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace DiscoBot |
|||
{ |
|||
public class DisBot |
|||
{ |
|||
private DiscordSocketClient discordClient; |
|||
private WSController wsC; |
|||
private List<Type> moduleTypes = new List<Type>(); |
|||
private Dictionary<ulong, Dictionary<string, Func<SocketMessage, string[], Task>>> guildcommands = new Dictionary<ulong, Dictionary<string, Func<SocketMessage, string[], Task>>>(); |
|||
public DisBot(WSController wsC) |
|||
{ |
|||
this.wsC = wsC; |
|||
} |
|||
|
|||
public async void Initialize() |
|||
{ |
|||
Console.WriteLine("In init!"); |
|||
discordClient = new DiscordSocketClient(); |
|||
|
|||
await discordClient.LoginAsync(TokenType.Bot, "NTUxNDcxODcxNDcyNjk3MzQ1.D3DVtw.Weh-a3l2XsBGuD5N1-rLKfnZ8wI"); |
|||
await discordClient.StartAsync(); |
|||
|
|||
discordClient.Log += Log; |
|||
discordClient.JoinedGuild += JoinedGuild; |
|||
discordClient.GuildAvailable += GuildAvailable; |
|||
discordClient.Connected += Connected; |
|||
discordClient.Ready += Ready; |
|||
discordClient.LeftGuild += LeftGuild; |
|||
discordClient.MessageReceived += MessageReceived; |
|||
|
|||
Console.WriteLine("Looking for modules..."); |
|||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); |
|||
foreach(Assembly a in assemblies) |
|||
{ |
|||
moduleTypes.AddRange(GetTypesWithInterface(a)); |
|||
} |
|||
Console.WriteLine("Found " + moduleTypes.Count + " module types."); |
|||
} |
|||
|
|||
private Task Log(LogMessage msg) |
|||
{ |
|||
Console.WriteLine(msg.ToString()); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task Connected() |
|||
{ |
|||
Console.WriteLine("CONNECTED WOHOOOO"); |
|||
return Task.CompletedTask; |
|||
} |
|||
private Task Ready() |
|||
{ |
|||
Console.WriteLine("Ready, connected to the following guilds: "); |
|||
foreach(SocketGuild guild in discordClient.Guilds) |
|||
{ |
|||
Console.WriteLine("- "+guild.Name+" | "+guild.Id); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task JoinedGuild(SocketGuild guild) |
|||
{ |
|||
Console.WriteLine("Joined Guild "+guild.Name); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private void LoadModulesForGuild(SocketGuild guild) |
|||
{ |
|||
Dictionary<string, Func<SocketMessage, string[], Task>> commands = new Dictionary<string, Func<SocketMessage, string[], Task>>(); |
|||
foreach (Type t in moduleTypes) |
|||
{ |
|||
if (t.IsInterface) |
|||
continue; |
|||
try |
|||
{ |
|||
IModule module = (IModule)Activator.CreateInstance(t, new object[] { guild }); |
|||
module.Initialize(wsC); |
|||
var c = module.Commands; |
|||
List<string> keyList = new List<string>(c.Keys); |
|||
foreach (string k in keyList) |
|||
{ |
|||
Console.WriteLine("Found Command: " + k); |
|||
} |
|||
foreach (var entry in c) |
|||
{ |
|||
commands.Add(entry.Key, entry.Value); |
|||
} |
|||
} catch(MissingMethodException) |
|||
{ |
|||
Console.WriteLine("ERROR: Could not instantiate " + t.FullName + ". Could not find SocketGuild constructor."); |
|||
} |
|||
} |
|||
guildcommands.Add(guild.Id, commands); |
|||
} |
|||
|
|||
private Task GuildAvailable(SocketGuild guild) |
|||
{ |
|||
Console.WriteLine("Guild available " + guild.Name + " | " + guild.Id); |
|||
if (guildcommands.ContainsKey(guild.Id)) |
|||
{ |
|||
Console.WriteLine("Guild " + guild.Id + " is already loaded."); |
|||
return Task.CompletedTask; |
|||
} |
|||
LoadModulesForGuild(guild); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task LeftGuild(SocketGuild guild) |
|||
{ |
|||
Console.WriteLine("Left Guild " + guild.Name); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task MessageReceived(SocketMessage message) |
|||
{ |
|||
// Don't parse our own messages
|
|||
if (message.Author.Id == discordClient.CurrentUser.Id) |
|||
return Task.CompletedTask; |
|||
|
|||
if(message.Content.StartsWith("!")) |
|||
{ |
|||
if(message.Channel is IGuildChannel) |
|||
{ |
|||
var gchan = message.Channel as IGuildChannel; |
|||
Console.WriteLine("|"+gchan.Name+"| <" + message.Author + "> " + message.Content); |
|||
Dictionary<string, Func<SocketMessage, string[], Task>> commands = guildcommands[gchan.GuildId]; |
|||
string cnt = message.Content.Substring(1); |
|||
string[] splits = cnt.Split(" "); |
|||
string command = splits[0]; |
|||
|
|||
return Task.Run(() => { |
|||
if(commands.ContainsKey(command)) |
|||
commands[command](message, splits); |
|||
else |
|||
message.Channel.SendMessageAsync("Command not found!"); |
|||
}); |
|||
} else |
|||
{ |
|||
Console.WriteLine("Out of guild command! "+message.Content); |
|||
message.Channel.SendMessageAsync("Commands in DM are not supported. :("); |
|||
} |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private IEnumerable<Type> GetTypesWithInterface(Assembly asm) |
|||
{ |
|||
var it = typeof(IModule); |
|||
return asm.GetLoadableTypes().Where(it.IsAssignableFrom).ToList(); |
|||
} |
|||
} |
|||
public static class TypeLoaderExtensions |
|||
{ |
|||
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) |
|||
{ |
|||
if (assembly == null) throw new ArgumentNullException("assembly"); |
|||
try |
|||
{ |
|||
return assembly.GetTypes(); |
|||
} |
|||
catch (ReflectionTypeLoadException e) |
|||
{ |
|||
return e.Types.Where(t => t != null); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.0</TargetFramework> |
|||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> |
|||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> |
|||
<IsPackable>false</IsPackable> |
|||
<SpaRoot>ClientApp\</SpaRoot> |
|||
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes> |
|||
|
|||
<!-- Set this to true if you enable server-side prerendering --> |
|||
<BuildServerSideRenderer>false</BuildServerSideRenderer> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Discord.Net" Version="2.0.1" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview3-19153-02" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0-preview3-19153-02" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0-preview4.19216.3" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0-preview4.19216.3" /> |
|||
<PackageReference Include="System.ServiceModel.Syndication" Version="4.6.0-preview4.19212.13" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<!-- Don't publish the SPA source files, but do show them in the project files list --> |
|||
<Content Remove="$(SpaRoot)**" /> |
|||
<None Remove="$(SpaRoot)**" /> |
|||
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" /> |
|||
</ItemGroup> |
|||
|
|||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> |
|||
<!-- Ensure Node.js is installed --> |
|||
<Exec Command="node --version" ContinueOnError="true"> |
|||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> |
|||
</Exec> |
|||
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> |
|||
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." /> |
|||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> |
|||
</Target> |
|||
|
|||
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> |
|||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode --> |
|||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> |
|||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" /> |
|||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " /> |
|||
|
|||
<!-- Include the newly-built files in the publish output --> |
|||
<ItemGroup> |
|||
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" /> |
|||
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" /> |
|||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> |
|||
<RelativePath>%(DistFiles.Identity)</RelativePath> |
|||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> |
|||
</ResolvedFileToPublish> |
|||
</ItemGroup> |
|||
</Target> |
|||
|
|||
</Project> |
@ -0,0 +1,14 @@ |
|||
using Discord.WebSocket; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace DiscoBot |
|||
{ |
|||
interface IModule |
|||
{ |
|||
Dictionary<string,Func<SocketMessage, string[], Task>> Commands { get; set; } |
|||
void Initialize(WSController wsC); |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
@page |
|||
@model ErrorModel |
|||
@{ |
|||
ViewData["Title"] = "Error"; |
|||
} |
|||
|
|||
<h1 class="text-danger">Error.</h1> |
|||
<h2 class="text-danger">An error occurred while processing your request.</h2> |
|||
|
|||
@if (Model.ShowRequestId) |
|||
{ |
|||
<p> |
|||
<strong>Request ID:</strong> <code>@Model.RequestId</code> |
|||
</p> |
|||
} |
|||
|
|||
<h3>Development Mode</h3> |
|||
<p> |
|||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred. |
|||
</p> |
|||
<p> |
|||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong> |
|||
It can result in displaying sensitive information from exceptions to end users. |
|||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> |
|||
and restarting the app. |
|||
</p> |
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
|
|||
namespace DiscoBot.Pages |
|||
{ |
|||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] |
|||
public class ErrorModel : PageModel |
|||
{ |
|||
public string RequestId { get; set; } |
|||
|
|||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); |
|||
|
|||
public void OnGet() |
|||
{ |
|||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
@using DiscoBot |
|||
@namespace DiscoBot.Pages |
|||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace DiscoBot |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static void Main(string[] args) |
|||
{ |
|||
CreateWebHostBuilder(args).Build().Run(); |
|||
} |
|||
|
|||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => |
|||
WebHost.CreateDefaultBuilder(args) |
|||
.UseStartup<Startup>(); |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"profiles": { |
|||
"DiscoBot": { |
|||
"commandName": "Project", |
|||
"launchBrowser": false, |
|||
"applicationUrl": "http://localhost:5000", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,114 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.SpaServices.AngularCli; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.WebSockets; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace DiscoBot |
|||
{ |
|||
public class Startup |
|||
{ |
|||
private List<WebSocket> webSockets = new List<WebSocket>(); |
|||
private WSController wsC = new WSController(); |
|||
private DisBot diBot; |
|||
public Startup(IConfiguration configuration) |
|||
{ |
|||
Configuration = configuration; |
|||
} |
|||
|
|||
public IConfiguration Configuration { get; } |
|||
|
|||
// This method gets called by the runtime. Use this method to add services to the container.
|
|||
public void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddMvc() |
|||
.AddNewtonsoftJson(); |
|||
|
|||
// In production, the Angular files will be served from this directory
|
|||
services.AddSpaStaticFiles(configuration => |
|||
{ |
|||
configuration.RootPath = "ClientApp/dist"; |
|||
}); |
|||
} |
|||
|
|||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
|||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) |
|||
{ |
|||
diBot = new DisBot(wsC); |
|||
Task.Run(() => |
|||
{ |
|||
diBot.Initialize(); |
|||
}); |
|||
if (env.IsDevelopment()) |
|||
{ |
|||
app.UseDeveloperExceptionPage(); |
|||
} |
|||
else |
|||
{ |
|||
app.UseExceptionHandler("/Error"); |
|||
} |
|||
|
|||
app.UseStaticFiles(); |
|||
|
|||
var webSocketOptions = new WebSocketOptions() |
|||
{ |
|||
KeepAliveInterval = TimeSpan.FromSeconds(120), |
|||
ReceiveBufferSize = 4 * 1024 |
|||
}; |
|||
|
|||
app.UseWebSockets(webSocketOptions); |
|||
|
|||
|
|||
|
|||
app.Use(async (context, next) => |
|||
{ |
|||
if (context.Request.Path == "/ws") |
|||
{ |
|||
if (context.WebSockets.IsWebSocketRequest) |
|||
{ |
|||
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); |
|||
webSockets.Add(webSocket); |
|||
//await Echo(context, webSocket);
|
|||
} |
|||
else |
|||
{ |
|||
context.Response.StatusCode = 400; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
await next(); |
|||
} |
|||
|
|||
}); |
|||
|
|||
app.UseSpaStaticFiles(); |
|||
|
|||
app.UseMvc(routes => |
|||
{ |
|||
routes.MapRoute( |
|||
name: "default", |
|||
template: "{controller}/{action=Index}/{id?}"); |
|||
}); |
|||
|
|||
app.UseSpa(spa => |
|||
{ |
|||
// To learn more about options for serving an Angular SPA from ASP.NET Core,
|
|||
// see https://go.microsoft.com/fwlink/?linkid=864501
|
|||
|
|||
spa.Options.SourcePath = "ClientApp"; |
|||
|
|||
if (env.IsDevelopment()) |
|||
{ |
|||
spa.UseAngularCliServer(npmScript: "start"); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace DiscoBot |
|||
{ |
|||
public class WSController |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Debug", |
|||
"System": "Information", |
|||
"Microsoft": "Information" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Warning" |
|||
} |
|||
}, |
|||
"AllowedHosts": "*" |
|||
} |
@ -0,0 +1,42 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace DiscoBot.calendar |
|||
{ |
|||
[Route("api/[controller]")]
|
|||
[ApiController] |
|||
public class CalController : Controller |
|||
{ |
|||
// GET: api/Cal
|
|||
[HttpGet("{guild}", Name = "Get")] |
|||
public JsonResult Get(ulong guild) |
|||
{ |
|||
CalendarContext calendarContext = new CalendarContext(guild); |
|||
List<CalendarItem> items = calendarContext.CalendarItems.Include(i => i.Attendance).ToList(); |
|||
return Json(items); |
|||
} |
|||
|
|||
// POST: api/Cal
|
|||
[HttpPost] |
|||
public void Post([FromBody] string value) |
|||
{ |
|||
} |
|||
|
|||
// PUT: api/Cal/5
|
|||
[HttpPut("{id}")] |
|||
public void Put(int id, [FromBody] string value) |
|||
{ |
|||
} |
|||
|
|||
// DELETE: api/ApiWithActions/5
|
|||
[HttpDelete("{id}")] |
|||
public void Delete(int id) |
|||
{ |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Discord; |
|||
using Discord.WebSocket; |
|||
|
|||
namespace DiscoBot.calendar |
|||
{ |
|||
public class Calendar : IModule |
|||
{ |
|||
private WSController wsC; |
|||
private SocketGuild guild; |
|||
public enum Attendance { Attending, Maybe } |
|||
private CalendarContext calendarContext; |
|||
public Dictionary<string, Func<SocketMessage, string[], Task>> Commands { get; set; } = new Dictionary<string, Func<SocketMessage, string[], Task>>(); |
|||
|
|||
public Calendar(SocketGuild guild) |
|||
{ |
|||
this.guild = guild; |
|||
calendarContext = new CalendarContext(guild.Id); |
|||
calendarContext.Database.EnsureCreated(); |
|||
|
|||
Commands.Add("caltest", HandleTestCommand); |
|||
Commands.Add("caladd", HandleAddEventCommand); |
|||
} |
|||
|
|||
public void Initialize(WSController wsC) |
|||
{ |
|||
this.wsC = wsC; |
|||
Console.WriteLine("Initializing calendar..."); |
|||
} |
|||
|
|||
private Task HandleTestCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
Console.WriteLine("Calendar: Handling test command!"); |
|||
string para = string.Join(",", parameters); |
|||
msg.Channel.SendMessageAsync("Test succeeded. Params: " + para); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleAddEventCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
if(parameters.Length != 4) |
|||
{ |
|||
msg.Channel.SendMessageAsync("Usage: caladd Name Date Comment"); |
|||
return Task.CompletedTask; |
|||
} |
|||
try |
|||
{ |
|||
var gchan = msg.Channel as IGuildChannel; |
|||
string name = parameters[1]; |
|||
string date = parameters[2]; |
|||
string comment = parameters[3]; |
|||
CalendarItem calItem = new CalendarItem(); |
|||
calItem.Channel = gchan.Id; |
|||
calItem.Name = name; |
|||
|
|||
calItem.Date = DateTimeOffset.Parse(date); |
|||
calItem.Comment = comment; |
|||
calItem.Attendance = new List<CalendarItemAttendance>(); |
|||
var attendance = new CalendarItemAttendance(); |
|||
attendance.Attendee = msg.Author.Id; |
|||
attendance.Attending = Attendance.Attending; |
|||
calItem.Attendance.Add(attendance); |
|||
calendarContext.CalendarItems.Add(calItem); |
|||
calendarContext.SaveChanges(); |
|||
} |
|||
catch(FormatException) |
|||
{ |
|||
msg.Channel.SendMessageAsync("Your date input was invalid. Try again with a valid date."); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using static DiscoBot.calendar.Calendar; |
|||
|
|||
namespace DiscoBot.calendar |
|||
{ |
|||
public class CalendarContext : DbContext |
|||
{ |
|||
private ulong guildId; |
|||
public DbSet<CalendarItem> CalendarItems { get; set; } |
|||
|
|||
public CalendarContext(ulong guildId) |
|||
{ |
|||
this.guildId = guildId; |
|||
} |
|||
|
|||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) |
|||
{ |
|||
optionsBuilder.UseSqlite("Data Source=" + guildId + "-calendar.db"); |
|||
} |
|||
} |
|||
|
|||
public class CalendarItemAttendance |
|||
{ |
|||
[Key] |
|||
public ulong Id { get; set; } |
|||
public ulong Attendee { get; set; } |
|||
public Attendance Attending { get; set; } |
|||
} |
|||
|
|||
|
|||
public class CalendarItem |
|||
{ |
|||
[Key] |
|||
public ulong Id { get; set; } |
|||
public string Name { get; set; } |
|||
public ulong Channel { get; set; } |
|||
public DateTimeOffset Date { get; set; } |
|||
public string Comment { get; set; } |
|||
public List<CalendarItemAttendance> Attendance { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,208 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.ServiceModel.Syndication; |
|||
using System.Threading.Tasks; |
|||
using System.Timers; |
|||
using System.Xml; |
|||
using Discord; |
|||
using Discord.WebSocket; |
|||
|
|||
namespace DiscoBot.rss |
|||
{ |
|||
public class Rss : IModule |
|||
{ |
|||
private SocketGuild guild; |
|||
private RssContext rssContext; |
|||
private Dictionary<string, Timer> timers = new Dictionary<string, Timer>(); |
|||
|
|||
public Dictionary<string, Func<SocketMessage, string[], Task>> Commands { get; set; } = new Dictionary<string, Func<SocketMessage, string[], Task>>(); |
|||
|
|||
public Rss(SocketGuild guild) |
|||
{ |
|||
this.guild = guild; |
|||
rssContext = new RssContext(guild.Id); |
|||
rssContext.Database.EnsureCreated(); |
|||
foreach(var f in rssContext.RssFeeds) |
|||
{ |
|||
InitializeFeed(f); |
|||
} |
|||
|
|||
Commands.Add("test",HandleTestCommand); |
|||
Commands.Add("rssadd", HandleRssAddCommand); |
|||
Commands.Add("rssdel", HandleRssDelCommand); |
|||
Commands.Add("rsslist", HandleRssListCommand); |
|||
Commands.Add("rsslistall", HandleRssListAllCommand); |
|||
} |
|||
|
|||
private Task InitializeFeed(RssFeed feed) |
|||
{ |
|||
Console.WriteLine("Found feed " + feed.Name); |
|||
Timer timer = new Timer(feed.CheckInterval.TotalMilliseconds); |
|||
timer.AutoReset = true; |
|||
timer.Elapsed += async (sender, e) => await HandleFeedCheck(feed); |
|||
timer.Start(); |
|||
timers.Add(feed.Name, timer); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task DeinitializeFeed(RssFeed feed) |
|||
{ |
|||
Timer t = timers[feed.Name]; |
|||
t.Stop(); |
|||
timers.Remove(feed.Name); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleFeedCheck(RssFeed feed) |
|||
{ |
|||
SocketTextChannel c = guild.Channels.Where(g => g.Id == feed.Channel).Single() as SocketTextChannel; |
|||
try |
|||
{ |
|||
XmlReader reader = XmlReader.Create(feed.Url); |
|||
SyndicationFeed f = SyndicationFeed.Load(reader); |
|||
DateTimeOffset newestItem = feed.LastChecked; |
|||
foreach(var item in f.Items) |
|||
{ |
|||
if (item.LastUpdatedTime <= feed.LastChecked) |
|||
continue; |
|||
Console.WriteLine("{0}: {1} | {2}", feed.Name, item.LastUpdatedTime, feed.LastChecked); |
|||
List<string> m = new List<string>(); |
|||
m.Add("<" + feed.Name + "> " + item.Title.Text); |
|||
if(item.LastUpdatedTime > newestItem) |
|||
{ |
|||
Console.WriteLine("^-- UPDATE!"); |
|||
newestItem = item.LastUpdatedTime; |
|||
} |
|||
foreach (var l in item.Links) |
|||
{ |
|||
m.Add(l.Uri.ToString()); |
|||
} |
|||
c.SendMessageAsync(string.Join(" | ", m)); |
|||
} |
|||
feed.LastChecked = newestItem; |
|||
rssContext.SaveChangesAsync(); |
|||
} catch(System.Net.WebException e) |
|||
{ |
|||
c.SendMessageAsync("<" + feed.Name + "> " + "NetworkFailure: " + e.Message); |
|||
} catch(XmlException e) |
|||
{ |
|||
c.SendMessageAsync("<" + feed.Name + "> " + "Malformed Response: " + e.Message); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleRssAddCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
var gchan = msg.Channel as IGuildChannel; |
|||
string name = parameters[1]; |
|||
string url = parameters[2]; |
|||
try |
|||
{ |
|||
int timeSec = Int32.Parse(parameters[3]); |
|||
RssFeed feed = new RssFeed(); |
|||
rssContext.RssFeeds.Add(feed); |
|||
feed.Name = name; |
|||
feed.Url = url; |
|||
feed.Channel = gchan.Id; |
|||
feed.CheckInterval = new TimeSpan(0, 0, timeSec); |
|||
feed.LastChecked = DateTimeOffset.Now; |
|||
try |
|||
{ |
|||
rssContext.SaveChanges(); |
|||
InitializeFeed(feed); |
|||
msg.Channel.SendMessageAsync("Feed " + feed.Name + " with url " + feed.Url + " saved."); |
|||
} |
|||
catch (InvalidOperationException) |
|||
{ |
|||
msg.Channel.SendMessageAsync("Unable to save rss feed."); |
|||
} |
|||
} |
|||
catch (FormatException) |
|||
{ |
|||
msg.Channel.SendMessageAsync("Unable to save rss feed. Invalid check time."); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleRssListCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
List<RssFeed> feeds = rssContext.RssFeeds.Where(f => f.Channel == msg.Channel.Id).ToList(); |
|||
List<string> m = new List<string>(); |
|||
m.Add("Feeds for this channel are:"); |
|||
m.Add("Name | URL | CheckInterval | LastChecked"); |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
m = new List<string>(); |
|||
int cnt = 0; |
|||
foreach (var f in feeds) |
|||
{ |
|||
m.Add("- " + f.Name + " | " + f.Url + " | " + f.CheckInterval + " | " + f.LastChecked); |
|||
cnt++; |
|||
if(cnt > 4) |
|||
{ |
|||
cnt = 0; |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
} |
|||
} |
|||
if(cnt > 0) |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleRssListAllCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
List<RssFeed> feeds = rssContext.RssFeeds.ToList(); |
|||
List<string> m = new List<string>(); |
|||
m.Add("Feeds for this guild are:"); |
|||
m.Add("Name | URL | Channel | CheckInterval | LastChecked"); |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
m = new List<string>(); |
|||
int cnt = 0; |
|||
foreach (var f in feeds) |
|||
{ |
|||
m.Add("- " + f.Name + " | " + f.Url + " | " + guild.Channels.Where(c => c.Id == f.Channel).Single() + " | " + f.CheckInterval + " | " + f.LastChecked); |
|||
cnt++; |
|||
if (cnt > 4) |
|||
{ |
|||
cnt = 0; |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
} |
|||
} |
|||
if(cnt > 0) |
|||
msg.Channel.SendMessageAsync(string.Join("\n", m)); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleRssDelCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
var gchan = msg.Channel as IGuildChannel; |
|||
string name = parameters[1]; |
|||
try |
|||
{ |
|||
RssFeed f = rssContext.RssFeeds.Where(f => f.Name == name).Single(); |
|||
rssContext.Remove(f); |
|||
rssContext.SaveChanges(); |
|||
DeinitializeFeed(f); |
|||
msg.Channel.SendMessageAsync("Removed feed " + f.Name); |
|||
} catch(InvalidOperationException) |
|||
{ |
|||
msg.Channel.SendMessageAsync("Could not find feed " + name); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private Task HandleTestCommand(SocketMessage msg, string[] parameters) |
|||
{ |
|||
Console.WriteLine("Handling test command!"); |
|||
string para = string.Join(",", parameters); |
|||
msg.Channel.SendMessageAsync("Test succeeded. Params: "+para); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public void Initialize(WSController wsC) |
|||
{ |
|||
Console.WriteLine("Initializing rss..."); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace DiscoBot.rss |
|||
{ |
|||
public class RssContext : DbContext |
|||
{ |
|||
private ulong guildId; |
|||
public DbSet<RssFeed> RssFeeds { get; set; } |
|||
|
|||
public RssContext(ulong guildId) |
|||
{ |
|||
this.guildId = guildId; |
|||
} |
|||
|
|||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) |
|||
{ |
|||
optionsBuilder.UseSqlite("Data Source=" + guildId + "-rss.db"); |
|||
} |
|||
} |
|||
|
|||
public class RssFeed |
|||
{ |
|||
[Key] |
|||
public string Name { get; set; } |
|||
public string Url { get; set; } |
|||
public ulong Channel { get; set; } |
|||
public TimeSpan CheckInterval { get; set; } |
|||
public DateTimeOffset LastChecked { get; set; } |
|||
|
|||
} |
|||
} |
After Width: | Height: | Size: 31 KiB |
Loading…
Reference in new issue