├── .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 | --------------------------------------------------------------------------------