├── .gitignore ├── .rubocop.yml ├── README.md ├── app ├── jobs │ └── regular │ │ └── create_github_linkback.rb └── lib │ └── github_linkback.rb ├── config ├── locales │ ├── server.en.yml │ └── server.it.yml └── settings.yml ├── plugin.rb └── spec └── lib └── github_linkback_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This plugin is deprecated. Please use [discourse-github](https://github.com/discourse/discourse-github) instead. 2 | 3 | # Discourse Github Linkback 4 | 5 | This plugin will create a link from a Github pull request or commit 6 | back to a Discourse post where it is mentioned. 7 | 8 | ## Installation 9 | 10 | Follow the [plugin installation guide](https://meta.discourse.org/t/install-a-plugin/19157?u=eviltrout). 11 | 12 | ## After Installation 13 | 14 | 1. You need to enable the plugin on Settings -> Plugins. 15 | 16 | 2. Generate an [access token](https://github.com/settings/tokens) on Github. 17 | Be sure to give it only the `public_repo` scope. Paste that token into the 18 | `github linkback access token` setting. 19 | 20 | 3. Finally, add the projects you wish to post to in the `github linkback projects` site setting in the formats: 21 | - `username/repository` for specific repositories 22 | - `username/*` for all repositories of a certain user 23 | -------------------------------------------------------------------------------- /app/jobs/regular/create_github_linkback.rb: -------------------------------------------------------------------------------- 1 | module Jobs 2 | class CreateGithubLinkback < Jobs::Base 3 | def execute(args) 4 | return unless SiteSetting.github_linkback_enabled? 5 | GithubLinkback.new(Post.find(args[:post_id])).create 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/lib/github_linkback.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'pretty_text' 2 | require 'digest/sha1' 3 | 4 | class GithubLinkback 5 | 6 | class Link 7 | attr_reader :url, :project, :type 8 | attr_accessor :sha, :pr_number 9 | 10 | def initialize(url, project, type) 11 | @url = url 12 | @project = project 13 | @type = type 14 | end 15 | end 16 | 17 | def initialize(post) 18 | @post = post 19 | end 20 | 21 | def should_enqueue? 22 | !!(SiteSetting.github_linkback_enabled? && 23 | @post.present? && 24 | @post.raw =~ /github/ && 25 | Guardian.new.can_see?(@post) && 26 | @post.topic.visible?) 27 | end 28 | 29 | def enqueue 30 | Jobs.enqueue(:create_github_linkback, post_id: @post.id) if should_enqueue? 31 | end 32 | 33 | def github_links 34 | projects = SiteSetting.github_linkback_projects.split('|') 35 | 36 | return [] if projects.blank? 37 | 38 | result = {} 39 | PrettyText.extract_links(@post.cooked).map(&:url).each do |l| 40 | next if @post.custom_fields[GithubLinkback.field_for(l)].present? 41 | 42 | if l =~ /https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/commit\/([0-9a-f]+)/ 43 | project = "#{Regexp.last_match[1]}/#{Regexp.last_match[2]}" 44 | if is_allowed_project_link?(projects, project) 45 | link = Link.new(Regexp.last_match[0], project, :commit) 46 | link.sha = Regexp.last_match[3] 47 | result[link.url] = link 48 | end 49 | elsif l =~ /https?:\/\/github.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)/ 50 | project = "#{Regexp.last_match[1]}/#{Regexp.last_match[2]}" 51 | if is_allowed_project_link?(projects, project) 52 | link = Link.new(Regexp.last_match[0], project, :pr) 53 | link.pr_number = Regexp.last_match[3].to_i 54 | result[link.url] = link 55 | end 56 | end 57 | end 58 | result.values 59 | end 60 | 61 | def is_allowed_project_link?(projects, project) 62 | return true if projects.include?(project) 63 | 64 | check_user = project.split("/")[0] 65 | projects.any? do |allowed_project| 66 | allowed_user, allowed_all_projects = allowed_project.split("/") 67 | (allowed_user == check_user) && (allowed_all_projects == "*") 68 | end 69 | end 70 | 71 | def create 72 | return [] unless SiteSetting.github_linkback_access_token.present? 73 | 74 | links = github_links 75 | links.each do |link| 76 | next unless [:commit, :pr].include?(link.type) 77 | 78 | if link.type == :commit 79 | post_commit(link) 80 | elsif link.type == :pr 81 | post_pr(link) 82 | end 83 | 84 | # Don't post the same link twice 85 | @post.custom_fields[GithubLinkback.field_for(link.url)] = 'true' 86 | @post.save_custom_fields 87 | end 88 | 89 | links 90 | end 91 | 92 | def self.field_for(url) 93 | "github-linkback:#{Digest::SHA1.hexdigest(url)[0..15]}" 94 | end 95 | 96 | private 97 | 98 | def post_pr(link) 99 | github_url = "https://api.github.com/repos/#{link.project}/issues/#{link.pr_number}/comments" 100 | comment = I18n.t( 101 | 'github_linkback.pr_template', 102 | title: SiteSetting.title, 103 | post_url: "#{Discourse.base_url}#{@post.url}" 104 | ) 105 | 106 | Excon.post( 107 | github_url, 108 | body: { body: comment }.to_json, 109 | headers: headers 110 | ) 111 | end 112 | 113 | def post_commit(link) 114 | github_url = "https://api.github.com/repos/#{link.project}/commits/#{link.sha}/comments" 115 | 116 | comment = I18n.t( 117 | 'github_linkback.commit_template', 118 | title: SiteSetting.title, 119 | post_url: "#{Discourse.base_url}#{@post.url}" 120 | ) 121 | 122 | Excon.post( 123 | github_url, 124 | body: { body: comment }.to_json, 125 | headers: headers 126 | ) 127 | end 128 | 129 | def headers 130 | { 131 | "Content-Type" => "application/json", 132 | "Authorization" => "token #{SiteSetting.github_linkback_access_token}", 133 | "User-Agent" => "Discourse-Github-Linkback" 134 | } 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /config/locales/server.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | site_settings: 3 | github_linkback_enabled: "Link github issues back to forum discussions" 4 | github_linkback_projects: "List of projects to link back from" 5 | github_linkback_access_token: "A valid access token for the user who will post the linkback" 6 | github_linkback: 7 | not_supported: 'The discourse-github-linkback plugin is no longer supported. Please migrate to the discourse-github plugin, which supports the same functionality.' 8 | commit_template: | 9 | This commit has been mentioned on **%{title}**. There might be relevant details there: 10 | 11 | %{post_url} 12 | pr_template: | 13 | This pull request has been mentioned on **%{title}**. There might be relevant details there: 14 | 15 | %{post_url} 16 | -------------------------------------------------------------------------------- /config/locales/server.it.yml: -------------------------------------------------------------------------------- 1 | it: 2 | site_settings: 3 | github_linkback_enabled: "Collega gli issue di github alle relative discussioni sul forum" 4 | github_linkback_projects: "Elenco dei progetti da collegare al forum" 5 | github_linkback_access_token: "Un token di accesso valido per l'utente che invierà il collegamento" 6 | github_linkback: 7 | commit_template: | 8 | This commit has been mentioned on **%{title}**. There might be relevant details there: 9 | 10 | %{post_url} 11 | pr_template: | 12 | This pull request has been mentioned on **%{title}**. There might be relevant details there: 13 | 14 | %{post_url} 15 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | github_linkback_enabled: 3 | default: false 4 | github_linkback_projects: 5 | default: "" 6 | type: list 7 | github_linkback_access_token: 8 | default: '' 9 | -------------------------------------------------------------------------------- /plugin.rb: -------------------------------------------------------------------------------- 1 | # name: discourse-github-linkback 2 | # about: Links Github content back to a Discourse discussion 3 | # version: 0.1 4 | # authors: Robin Ward 5 | # url: https://github.com/discourse/discourse-github-linkback 6 | 7 | enabled_site_setting :github_linkback_enabled 8 | 9 | after_initialize do 10 | AdminDashboardData.add_problem_check do 11 | I18n.t("github_linkback.not_supported") 12 | end 13 | 14 | require_dependency File.expand_path('../app/lib/github_linkback.rb', __FILE__) 15 | require_dependency File.expand_path('../app/jobs/regular/create_github_linkback.rb', __FILE__) 16 | 17 | DiscourseEvent.on(:post_created) do |post| 18 | GithubLinkback.new(post).enqueue 19 | end 20 | 21 | DiscourseEvent.on(:post_edited) do |post| 22 | GithubLinkback.new(post).enqueue 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/github_linkback_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe GithubLinkback do 4 | let(:github_commit_link) { "https://github.com/discourse/discourse/commit/76981605fa10975e2e7af457e2f6a31909e0c811" } 5 | let(:github_pr_link) { "https://github.com/discourse/discourse/pull/701" } 6 | let(:github_pr_link_wildcard) { "https://github.com/discourse/discourse-github-linkback/pull/3" } 7 | 8 | let(:post) do 9 | Fabricate( 10 | :post, 11 | raw: <<~RAW 12 | cool post 13 | 14 | #{github_commit_link} 15 | 16 | https://eviltrout.com/not-a-gh-link 17 | 18 | #{github_commit_link} 19 | 20 | https://github.com/eviltrout/tis-100/commit/e22b23f354e3a1c31bc7ad37a6a309fd6daf18f4 21 | 22 | #{github_pr_link} 23 | 24 | i have no idea what i'm linking back to 25 | 26 | #{github_pr_link_wildcard} 27 | 28 | end_of_transmission 29 | 30 | RAW 31 | ) 32 | end 33 | 34 | context "#should_enqueue?" do 35 | let(:post_without_link) { Fabricate.build(:post) } 36 | 37 | let(:post_with_link) do 38 | Fabricate.build(:post, raw: 'https://github.com/discourse/discourse/commit/5be9bee2307dd517c26e6ef269471aceba5d5acf') 39 | end 40 | 41 | it "returns false when the feature is disabled" do 42 | SiteSetting.github_linkback_enabled = false 43 | expect(GithubLinkback.new(post_with_link).should_enqueue?).to eq(false) 44 | end 45 | 46 | it "returns false without a post" do 47 | SiteSetting.github_linkback_enabled = true 48 | expect(GithubLinkback.new(nil).should_enqueue?).to eq(false) 49 | end 50 | 51 | it "returns false when the post doesn't have the word github in it" do 52 | SiteSetting.github_linkback_enabled = true 53 | expect(GithubLinkback.new(post_without_link).should_enqueue?).to eq(false) 54 | end 55 | 56 | it "returns true when the feature is enabled" do 57 | SiteSetting.github_linkback_enabled = true 58 | expect(GithubLinkback.new(post_with_link).should_enqueue?).to eq(true) 59 | end 60 | 61 | context "private_message" do 62 | it "doesn't enqueue private messages" do 63 | SiteSetting.github_linkback_enabled = true 64 | private_topic = Fabricate(:private_message_topic) 65 | private_post = Fabricate( 66 | :post, 67 | topic: private_topic, 68 | raw: "this post http://github.com should not enqueue" 69 | ) 70 | expect(GithubLinkback.new(private_post).should_enqueue?).to eq(false) 71 | end 72 | end 73 | 74 | context "unlisted topics" do 75 | it "doesn't enqueue unlisted topics" do 76 | SiteSetting.github_linkback_enabled = true 77 | unlisted_topic = Fabricate(:topic, visible: false) 78 | unlisted_post = Fabricate( 79 | :post, 80 | topic: unlisted_topic, 81 | raw: "this post http://github.com should not enqueue" 82 | ) 83 | expect(GithubLinkback.new(unlisted_post).should_enqueue?).to eq(false) 84 | end 85 | end 86 | end 87 | 88 | context "#github_urls" do 89 | it "returns an empty array with no projects" do 90 | SiteSetting.github_linkback_projects = "" 91 | links = GithubLinkback.new(post).github_links 92 | expect(links).to eq([]) 93 | end 94 | 95 | it "doesn't return links that have already been posted" do 96 | SiteSetting.github_linkback_projects = "discourse/discourse|eviltrout/ember-performance|discourse/*" 97 | 98 | post.custom_fields[GithubLinkback.field_for(github_commit_link)] = "true" 99 | post.custom_fields[GithubLinkback.field_for(github_pr_link)] = "true" 100 | post.custom_fields[GithubLinkback.field_for(github_pr_link_wildcard)] = "true" 101 | post.save_custom_fields 102 | 103 | links = GithubLinkback.new(post).github_links 104 | expect(links.size).to eq(0) 105 | end 106 | 107 | it "should return the urls for the selected projects" do 108 | SiteSetting.github_linkback_projects = "discourse/discourse|eviltrout/ember-performance|discourse/*" 109 | links = GithubLinkback.new(post).github_links 110 | expect(links.size).to eq(3) 111 | 112 | expect(links[0].url).to eq(github_commit_link) 113 | expect(links[0].project).to eq("discourse/discourse") 114 | expect(links[0].sha).to eq("76981605fa10975e2e7af457e2f6a31909e0c811") 115 | expect(links[0].type).to eq(:commit) 116 | 117 | expect(links[1].url).to eq(github_pr_link) 118 | expect(links[1].project).to eq("discourse/discourse") 119 | expect(links[1].pr_number).to eq(701) 120 | expect(links[1].type).to eq(:pr) 121 | 122 | expect(links[2].url).to eq(github_pr_link_wildcard) 123 | expect(links[2].project).to eq("discourse/discourse-github-linkback") 124 | expect(links[2].pr_number).to eq(3) 125 | expect(links[2].type).to eq(:pr) 126 | end 127 | end 128 | 129 | context "#create" do 130 | before do 131 | SiteSetting.github_linkback_projects = "discourse/discourse|discourse/*" 132 | end 133 | 134 | it "returns an empty array without an access token" do 135 | expect(GithubLinkback.new(post).create).to be_blank 136 | end 137 | 138 | context "with an access token" do 139 | let(:headers) { 140 | { 'Authorization' => 'token abcdef', 141 | 'Content-Type' => 'application/json', 142 | 'Host' => 'api.github.com', 143 | 'User-Agent' => 'Discourse-Github-Linkback' } 144 | } 145 | 146 | before do 147 | SiteSetting.github_linkback_access_token = "abcdef" 148 | 149 | stub_request(:post, "https://api.github.com/repos/discourse/discourse/commits/76981605fa10975e2e7af457e2f6a31909e0c811/comments"). 150 | with(headers: headers). 151 | to_return(status: 200, body: "", headers: {}) 152 | 153 | stub_request(:post, "https://api.github.com/repos/discourse/discourse/issues/701/comments"). 154 | with(headers: headers). 155 | to_return(status: 200, body: "", headers: {}) 156 | 157 | stub_request(:post, "https://api.github.com/repos/discourse/discourse-github-linkback/issues/3/comments"). 158 | with(headers: headers). 159 | to_return(status: 200, body: "", headers: {}) 160 | 161 | end 162 | 163 | it "returns the URL it linked to and custom fields" do 164 | links = GithubLinkback.new(post).create 165 | expect(links.size).to eq(3) 166 | 167 | expect(links[0].url).to eq(github_commit_link) 168 | field = GithubLinkback.field_for(github_commit_link) 169 | expect(post.custom_fields[field]).to be_present 170 | 171 | expect(links[1].url).to eq(github_pr_link) 172 | field = GithubLinkback.field_for(github_pr_link) 173 | expect(post.custom_fields[field]).to be_present 174 | 175 | expect(links[2].url).to eq(github_pr_link_wildcard) 176 | field = GithubLinkback.field_for(github_pr_link_wildcard) 177 | expect(post.custom_fields[field]).to be_present 178 | end 179 | end 180 | end 181 | 182 | end 183 | --------------------------------------------------------------------------------