├── .gitattributes
├── .gitignore
├── CODE_OF_CONDUCT.md
├── JekyllBlogCommentsAzure.sln
├── JekyllBlogCommentsAzure
├── Comment.cs
├── JekyllBlogCommentsAzure.csproj
├── PostCommentToPullRequestFunction.cs
├── SentimentAnalysis.cs
├── WebConfigurator.cs
└── host.json
├── LICENSE.txt
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Azure Functions localsettings file
2 | local.settings.json
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # Build Results of an ATL Project
35 | [Dd]ebugPS/
36 | [Rr]eleasePS/
37 | dlldata.c
38 |
39 | *_i.c
40 | *_p.c
41 | *_i.h
42 | *.ilk
43 | *.meta
44 | *.obj
45 | *.pch
46 | *.pdb
47 | *.pgc
48 | *.pgd
49 | *.rsp
50 | *.sbr
51 | *.tlb
52 | *.tli
53 | *.tlh
54 | *.tmp
55 | *.tmp_proj
56 | *.log
57 | *.vspscc
58 | *.vssscc
59 | .builds
60 | *.pidb
61 | *.svclog
62 | *.scc
63 |
64 | # Visual Studio profiler
65 | *.psess
66 | *.vsp
67 | *.vspx
68 | *.sap
69 |
70 | # ReSharper is a .NET coding add-in
71 | _ReSharper*/
72 | *.[Rr]e[Ss]harper
73 | *.DotSettings.user
74 |
75 | # Click-Once directory
76 | publish/
77 |
78 | # Publish Web Output
79 | *.[Pp]ublish.xml
80 | *.azurePubxml
81 | *.pubxml
82 | *.publishproj
83 |
84 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
85 | # checkin your Azure Web App publish settings, but sensitive information contained
86 | # in these scripts will be unencrypted
87 | PublishScripts/
88 |
89 | # NuGet Packages
90 | *.nupkg
91 | # The packages folder can be ignored because of Package Restore
92 | **/packages/*
93 | # except build/, which is used as an MSBuild target.
94 | !**/packages/build/
95 | # Uncomment if necessary however generally it will be regenerated when needed
96 | #!**/packages/repositories.config
97 | # NuGet v3's project.json files produces more ignoreable files
98 | *.nuget.props
99 | *.nuget.targets
100 |
101 | # Microsoft Azure Build Output
102 | csx/
103 | *.build.csdef
104 |
105 | # Microsoft Azure Emulator
106 | ecf/
107 | rcf/
108 |
109 | # Visual Studio cache files
110 | # files ending in .cache can be ignored
111 | *.[Cc]ache
112 | # but keep track of directories ending in .cache
113 | !*.[Cc]ache/
114 |
115 | # Others
116 | ClientBin/
117 | ~$*
118 | *~
119 | *.dbmdl
120 | *.dbproj.schemaview
121 | *.jfm
122 | *.pfx
123 | *.publishsettings
124 | node_modules/
125 | orleans.codegen.cs
126 |
127 | # Backup & report files from converting an old project file
128 | # to a newer Visual Studio version. Backup files are not needed,
129 | # because we have git ;-)
130 | _UpgradeReport_Files/
131 | Backup*/
132 | UpgradeLog*.XML
133 | UpgradeLog*.htm
134 |
135 | # SQL Server files
136 | *.mdf
137 | *.ldf
138 |
139 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at damieng@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27520.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JekyllBlogCommentsAzure", "JekyllBlogCommentsAzure\JekyllBlogCommentsAzure.csproj", "{D71CC922-08B2-4C1A-B0D1-4AEF57E54765}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{464BAC08-68A7-40BA-91F0-856239A43789}"
9 | ProjectSection(SolutionItems) = preProject
10 | .gitattributes = .gitattributes
11 | .gitignore = .gitignore
12 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
13 | LICENSE = LICENSE
14 | README.md = README.md
15 | EndProjectSection
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {D71CC922-08B2-4C1A-B0D1-4AEF57E54765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {D71CC922-08B2-4C1A-B0D1-4AEF57E54765}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {D71CC922-08B2-4C1A-B0D1-4AEF57E54765}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {D71CC922-08B2-4C1A-B0D1-4AEF57E54765}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(SolutionProperties) = preSolution
29 | HideSolutionNode = FALSE
30 | EndGlobalSection
31 | GlobalSection(ExtensibilityGlobals) = postSolution
32 | SolutionGuid = {07AE55D5-6AA0-4CDC-80A1-0D3B6329BEF8}
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/Comment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.ComponentModel;
5 | using System.Configuration;
6 | using System.Linq;
7 | using System.Text.RegularExpressions;
8 | using YamlDotNet.Serialization;
9 |
10 | namespace JekyllBlogCommentsAzure
11 | {
12 | ///
13 | /// Represents a Comment to be written to the repository in YML format.
14 | ///
15 | class Comment
16 | {
17 | struct MissingRequiredValue { } // Placeholder for missing required form values
18 | static readonly Regex validEmail = new Regex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$"); // Simplest form of email validation
19 | static readonly Regex invalidPathChars = new Regex(@"[^a-zA-Z0-9-]"); // Matches invalid characters when mapping from the blog post slug to a file path
20 |
21 | ///
22 | /// Try to create a Comment from the form. Each Comment constructor argument will be name-matched
23 | /// against values in the form. Each non-optional arguments (those that don't have a default value)
24 | /// not supplied will cause an error in the list of errors and prevent the Comment from being created.
25 | ///
26 | /// Incoming form submission as a .
27 | /// Created if no errors occurred.
28 | /// A list containing any potential validation errors.
29 | /// True if the Comment was able to be created, false if validation errors occurred.
30 | public static bool TryCreateFromForm(NameValueCollection form, out Comment comment, out List errors)
31 | {
32 | var constructor = typeof(Comment).GetConstructors()[0];
33 | var values = constructor.GetParameters()
34 | .ToDictionary(
35 | p => p.Name,
36 | p => ConvertParameter(form[p.Name], p.ParameterType) ?? (p.HasDefaultValue ? p.DefaultValue : new MissingRequiredValue())
37 | );
38 |
39 | errors = values.Where(p => p.Value is MissingRequiredValue).Select(p => $"Form value missing for {p.Key}").ToList();
40 | if (values["email"] is string s && !validEmail.IsMatch(s))
41 | errors.Add("email not in correct format");
42 |
43 | comment = errors.Any() ? null : (Comment)constructor.Invoke(values.Values.ToArray());
44 | var isFormValid = !errors.Any();
45 | var config = PostCommentToPullRequestFunction.config;
46 |
47 | if (isFormValid && !string.IsNullOrEmpty(config.SentimentAnalysisSubscriptionKey))
48 | {
49 | var textAnalysis = new SentimentAnalysis(config.SentimentAnalysisSubscriptionKey,
50 | config.SentimentAnalysisRegion,
51 | config.SentimentAnalysisLang);
52 | comment.score = textAnalysis.Analyze(comment.message);
53 | }
54 | else
55 | {
56 | comment.score = "Not configured";
57 | }
58 |
59 | return isFormValid;
60 | }
61 |
62 | private static object ConvertParameter(string parameter, Type targetType)
63 | {
64 | return String.IsNullOrWhiteSpace(parameter)
65 | ? null
66 | : TypeDescriptor.GetConverter(targetType).ConvertFrom(parameter);
67 | }
68 |
69 | public Comment(string post_id, string message, string name, string email = null, Uri url = null, string avatar = null)
70 | {
71 | this.post_id = invalidPathChars.Replace(post_id, "-");
72 | this.message = message;
73 | this.name = name;
74 | this.email = email;
75 | this.url = url;
76 |
77 | date = DateTime.UtcNow;
78 | id = new { this.post_id, this.name, this.message, date }.GetHashCode().ToString("x8");
79 | if (Uri.TryCreate(avatar, UriKind.Absolute, out Uri avatarUrl))
80 | this.avatar = avatarUrl;
81 | }
82 |
83 | [YamlIgnore]
84 | public string post_id { get; }
85 |
86 | public string id { get; }
87 | public DateTime date { get; }
88 | public string name { get; }
89 | public string email { get; }
90 | public string score { get; set; }
91 |
92 | [YamlMember(typeof(string))]
93 | public Uri avatar { get; }
94 |
95 | [YamlMember(typeof(string))]
96 | public Uri url { get; }
97 |
98 | public string message { get; }
99 |
100 | public string ToYaml()
101 | {
102 | return new SerializerBuilder().Build().Serialize(this);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/JekyllBlogCommentsAzure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net461
4 | Damien Guard
5 |
6 |
7 | IDE1006;1701;1702;1705
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.Configuration.dll
18 |
19 |
20 |
21 |
22 | PreserveNewest
23 |
24 |
25 | PreserveNewest
26 | Never
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/PostCommentToPullRequestFunction.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.WebJobs;
2 | using Microsoft.Azure.WebJobs.Extensions.Http;
3 | using Octokit;
4 | using System;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 |
10 | namespace JekyllBlogCommentsAzure
11 | {
12 | public static class PostCommentToPullRequestFunction
13 | {
14 | public static readonly WebConfigurator config = new WebConfigurator();
15 |
16 | [FunctionName("PostComment")] // Actual form post handler
17 | public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestMessage request)
18 | {
19 | var form = await request.Content.ReadAsFormDataAsync();
20 |
21 | // Make sure the site posting the comment is the correct site.
22 | var allowedSite = config.CommentWebsiteUrl;
23 | var postedSite = form["comment-site"];
24 | if (!String.IsNullOrWhiteSpace(allowedSite) {
25 | if (String.IsNullOrWhiteSpace(postedSite))
26 | return request.CreateErrorResponse(HttpStatusCode.BadRequest, $"This Jekyll comments receiever is set to only allow specific sites and no 'comment-site' form value as provided.");
27 | if (!AreSameSites(allowedSite, postedSite))
28 | return request.CreateErrorResponse(HttpStatusCode.BadRequest, $"This Jekyll comments receiever does not handle forms for '{postedSite}'. You should point to your own instance.");
29 | }
30 |
31 | if (Comment.TryCreateFromForm(form, out var comment, out var errors))
32 | await CreatePullRequest(comment);
33 |
34 | if (errors.Any())
35 | return request.CreateErrorResponse(HttpStatusCode.BadRequest, String.Join("\n", errors));
36 |
37 | if (!Uri.TryCreate(form["redirect"], UriKind.Absolute, out var redirectUri))
38 | return request.CreateResponse(HttpStatusCode.OK);
39 |
40 | var response = request.CreateResponse(HttpStatusCode.Redirect);
41 | response.Headers.Location = redirectUri;
42 | return response;
43 | }
44 |
45 | [FunctionName("Preload")] // Ping this to preload the function and avoid cold starts.
46 | public static HttpResponseMessage Preload([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestMessage request)
47 | {
48 | var response = request.CreateResponse(HttpStatusCode.OK);
49 | return response;
50 | }
51 |
52 | private static bool AreSameSites(string commentSite, string postedCommentSite)
53 | {
54 | return Uri.TryCreate(commentSite, UriKind.Absolute, out var commentSiteUri)
55 | && Uri.TryCreate(postedCommentSite, UriKind.Absolute, out var postedCommentSiteUri)
56 | && commentSiteUri.Host.Equals(postedCommentSiteUri.Host, StringComparison.OrdinalIgnoreCase);
57 | }
58 |
59 | private static async Task CreatePullRequest(Comment comment)
60 | {
61 | // Create the Octokit client
62 | var github = new GitHubClient(new ProductHeaderValue("PostCommentToPullRequest"),
63 | new Octokit.Internal.InMemoryCredentialStore(new Credentials(config.GitHubToken)));
64 |
65 | // Get a reference to our GitHub repository
66 | var repoOwnerName = config.PullRequestRepository.Split('/');
67 | var repo = await github.Repository.Get(repoOwnerName[0], repoOwnerName[1]);
68 |
69 | // Create a new branch from the default branch
70 | var defaultBranch = await github.Repository.Branch.Get(repo.Id, repo.DefaultBranch);
71 | var newBranch = await github.Git.Reference.Create(repo.Id, new NewReference($"refs/heads/comment-{comment.id}", defaultBranch.Commit.Sha));
72 |
73 | // Create a new file with the comments in it
74 | var fileRequest = new CreateFileRequest($"Comment by {comment.name} on {comment.post_id}", comment.ToYaml(), newBranch.Ref)
75 | {
76 | Committer = new Committer(comment.name, comment.email ?? config.CommentFallbackCommitEmail ?? "redacted@example.com", comment.date)
77 | };
78 | await github.Repository.Content.CreateFile(repo.Id, $"_data/comments/{comment.post_id}/{comment.id}.yml", fileRequest);
79 |
80 | // Create a pull request for the new branch and file
81 | return await github.Repository.PullRequest.Create(repo.Id, new NewPullRequest(fileRequest.Message, newBranch.Ref, defaultBranch.Name)
82 | {
83 | Body = $"avatar:
\n\nScore: {comment.score}\n\n{comment.message}"
84 | });
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/SentimentAnalysis.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
8 | using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
9 | using Microsoft.Rest;
10 |
11 | namespace JekyllBlogCommentsAzure
12 | {
13 | public class SentimentAnalysis
14 | {
15 | private readonly string _subscriptionKey;
16 | private readonly string _region;
17 | private readonly string _lang;
18 |
19 | public SentimentAnalysis(string subscriptionKey, string region, string lang)
20 | {
21 | _subscriptionKey = subscriptionKey;
22 | _region = region;
23 | _lang = lang;
24 | }
25 |
26 | class ApiKeyServiceClientCredentials : ServiceClientCredentials
27 | {
28 | private readonly string _subscriptionKey;
29 |
30 |
31 | public ApiKeyServiceClientCredentials(string subscriptionKey)
32 | {
33 | _subscriptionKey = subscriptionKey;
34 | }
35 | public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
36 | {
37 | request.Headers.Add("Ocp-Apim-Subscription-Key", _subscriptionKey);
38 | return base.ProcessHttpRequestAsync(request, cancellationToken);
39 | }
40 | }
41 |
42 | public IEnumerable SplitFiveHundredChars(string input)
43 | {
44 | int id = 0;
45 | for (var i = 0; i < input.Length; i += 5000)
46 | {
47 | yield return new MultiLanguageInput(_lang, id.ToString(), input.Substring(i, Math.Min(5000, input.Length - i)));
48 | id++;
49 | }
50 | }
51 |
52 | public string Analyze(string input)
53 | {
54 | ITextAnalyticsClient client = new TextAnalyticsClient(new ApiKeyServiceClientCredentials(_subscriptionKey))
55 | {
56 | Endpoint = $"https://{_region}.api.cognitive.microsoft.com"
57 | };
58 | SentimentBatchResult result = client.SentimentAsync(
59 | new MultiLanguageBatchInput(
60 | SplitFiveHundredChars(input).ToList()
61 | )).Result;
62 | return $"{result.Documents[0].Score:0.00}";
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/WebConfigurator.cs:
--------------------------------------------------------------------------------
1 | using System.Configuration;
2 |
3 | namespace JekyllBlogCommentsAzure
4 | {
5 | public class WebConfigurator
6 | {
7 | public string CommentWebsiteUrl => ConfigurationManager.AppSettings["CommentWebsiteUrl"];
8 |
9 | public string GitHubToken => ConfigurationManager.AppSettings["GitHubToken"];
10 |
11 | public string PullRequestRepository => ConfigurationManager.AppSettings["PullRequestRepository"];
12 |
13 | public string CommentFallbackCommitEmail => ConfigurationManager.AppSettings["CommentFallbackCommitEmail"];
14 |
15 | public string SentimentAnalysisSubscriptionKey => ConfigurationManager.AppSettings["SentimentAnalysis.SubscriptionKey"];
16 |
17 | public string SentimentAnalysisRegion => ConfigurationManager.AppSettings["SentimentAnalysis.Region"];
18 |
19 | public string SentimentAnalysisLang => ConfigurationManager.AppSettings["SentimentAnalysis.Lang"];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/JekyllBlogCommentsAzure/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": [ "PostComment" ]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Damien Guard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jekyll Blog Comments Azure Function
2 |
3 | An Azure Function App that receives comment form posts and creates a pull request against your GitHub repository as part of the [jekyll-blog-comments](https://github.com/damieng/jekyll-blog-comments) system.
4 |
5 | The app includes just one function:
6 |
7 | * `PostComment` - receives form POST submission and creates a PR to add the comment to your Jekyll site
8 |
9 | ## Setup
10 |
11 | To set this up, you'll need to have an [Azure Portal account](https://portal.azure.com).
12 |
13 | 1. Fork this repository
14 | 2. [Create a **v1** Azure Function](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function)
15 | 3. [Create subscription key in Microsoft Azure Recognition](https://docs.microsoft.com/en-us/azure/cognitive-services/cognitive-services-apis-create-account) **optional**
16 | 4. [Set up your function to deploy from your fork](https://docs.microsoft.com/en-us/azure/azure-functions/scripts/functions-cli-create-function-app-github-continuous)
17 | 5. Set up the following [App Settings for your Azure Function](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings)
18 |
19 | | Setting | Value
20 | | -------- | -------
21 | | `PullRequestRepository` | `owner/name` of the repository that houses your Jekyll site for pull requests to be created against. For example, `haacked/haacked.com` will post to https://github.com/haacked/haacked.com
22 | | `GitHubToken` | A [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with access to edit your target repository.
23 | | `CommentWebsiteUrl` | The URL to the website that hosts the comments. This is used to make sure the correct site is posting comments to the receiver.
24 | | `CommentFallbackCommitEmail` | The email address to use for GitHub commits and PR's if the form does not supply one.
25 | | `SentimentAnalysis.SubscriptionKey` | Subscription Key for Microsoft Azure Recognition, if you don't want to use, just leave empty.
26 | | `SentimentAnalysis.Region` | Region for your Subscription key (E.g.: westus)
27 | | `SentimentAnalysis.Lang` | Language for comment, [find lang code here](https://docs.microsoft.com/en-us/azure/cognitive-services/text-analytics/language-support)
28 |
--------------------------------------------------------------------------------