Add stackoverflow plugin (#159)
This commit is contained in:
36
source/plugins/stackoverflow/README.md
Normal file
36
source/plugins/stackoverflow/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
### 🗨️ Stackoverflow plugin
|
||||
|
||||
The *stackoverflow* plugin lets you display your metrics, questions and answer from [stackoverflow](https://stackoverflow.com/).
|
||||
|
||||
<table>
|
||||
<td align="center">
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stackoverflow.svg">
|
||||
<img width="900" height="1" alt="">
|
||||
</td>
|
||||
</table>
|
||||
|
||||
<details>
|
||||
<summary>💬 Get your user id</summary>
|
||||
|
||||
Go to [stackoverflow.com](https://stackoverflow.com/) and click on your account profile.
|
||||
|
||||
Your user id will be in both url and search bar.
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
#### ℹ️ Examples workflows
|
||||
|
||||
[➡️ Available options for this plugin](metadata.yml)
|
||||
|
||||
```yaml
|
||||
- uses: lowlighter/metrics@latest
|
||||
with:
|
||||
# ... other options
|
||||
plugin_stackoverflow: yes
|
||||
plugin_stackoverflow_user: 8332505 # Stackoverflow user id (required)
|
||||
plugin_stackoverflow_sections: answers-top, questions-recent # Display top answers and recent questions
|
||||
plugin_stackoverflow_limit: 2 # Display 2 entries per section
|
||||
plugin_stackoverflow_lines: 4 # Display 4 lines per entry
|
||||
```
|
||||
97
source/plugins/stackoverflow/index.mjs
Normal file
97
source/plugins/stackoverflow/index.mjs
Normal file
@@ -0,0 +1,97 @@
|
||||
//Setup
|
||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
||||
//Plugin execution
|
||||
try {
|
||||
//Check if plugin is enabled and requirements are met
|
||||
if ((!enabled)||(!q.stackoverflow))
|
||||
return null
|
||||
|
||||
//Load inputs
|
||||
let {sections, user, limit, lines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||
if (!user)
|
||||
throw {error:{message:"You must provide a stackoverflow user id"}}
|
||||
|
||||
//Initialization
|
||||
//See https://api.stackexchange.com/docs
|
||||
const api = {base:"https://api.stackexchange.com/2.2", user:`https://api.stackexchange.com/2.2/users/${user}`}
|
||||
const filters = {user:"!0Z-LvgkLYnTCu1858)*D0lcx2", answer:"!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question:"!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"}
|
||||
const result = {sections, lines}
|
||||
|
||||
//Stackoverflow user metrics
|
||||
{
|
||||
//Account metrics
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`)
|
||||
const {data:{items:[{reputation, badge_counts:{bronze, silver, gold}, answer_count:answers, question_count:questions, view_count:views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`)
|
||||
const {data:{total:comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`)
|
||||
//Save result
|
||||
result.user = {reputation, badges:bronze+silver+gold, questions, answers, comments, views}
|
||||
}
|
||||
|
||||
//Answers
|
||||
for (const {key, sort} of [{key:"answers-recent", sort:"sort=activity&order=desc"}, {key:"answers-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
||||
//Load and format answers
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`)
|
||||
result[key] = items.map(item => format.answer(item, {imports, data}))
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||
//Load related questions
|
||||
const ids = result[key].map(({question_id}) => question_id).filter(id => id)
|
||||
if (ids) {
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`)
|
||||
items.map(item => format.question(item, {imports, data}))
|
||||
}
|
||||
}
|
||||
|
||||
//Questions
|
||||
for (const {key, sort} of [{key:"questions-recent", sort:"sort=activity&order=desc"}, {key:"questions-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
||||
//Load and format questions
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`)
|
||||
result[key] = items.map(item => format.question(item, {imports, data}))
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||
//Load related answers
|
||||
const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id)
|
||||
if (ids) {
|
||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||
const {data:{items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`)
|
||||
items.map(item => format.answer(item, {imports, data}))
|
||||
}
|
||||
}
|
||||
|
||||
//Results
|
||||
return result
|
||||
}
|
||||
//Handle errors
|
||||
catch (error) {
|
||||
if (error.error?.message)
|
||||
throw error
|
||||
throw {error:{message:"An error occured", instance:error}}
|
||||
}
|
||||
}
|
||||
|
||||
//Formatters
|
||||
const format = {
|
||||
/**Cached */
|
||||
cached:new Map(),
|
||||
/**Format answers */
|
||||
answer({body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, is_accepted:accepted, comment_count:comments = 0, creation_date, owner:{display_name:author}, link, answer_id:id, question_id}, {imports, data}) {
|
||||
const formatted = {type:"answer", body:imports.htmlunescape(body), score, upvotes, downvotes, accepted, comments, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, question_id,
|
||||
get question() {
|
||||
return format.cached.get(`q${this.question_id}`) ?? null
|
||||
},
|
||||
}
|
||||
this.cached.set(`a${id}`, formatted)
|
||||
return formatted
|
||||
},
|
||||
/**Format questions */
|
||||
question({title, body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, favorite_count:favorites, tags, is_answered:answered, answer_count:answers, comment_count:comments, view_count:views, creation_date, owner:{display_name:author}, link, question_id:id, accepted_answer_id = null}, {imports, data}) {
|
||||
const formatted = {type:"question", title:imports.htmlunescape(title), body:imports.htmlunescape(body), score, upvotes, downvotes, favorites, tags, answered, answers, comments, views, author, created:imports.date(creation_date*1000, {dateStyle:"short", timeZone:data.config.timezone?.name}), link, id, accepted_answer_id,
|
||||
get answer() {
|
||||
return format.cached.get(`a${this.accepted_answer_id}`) ?? null
|
||||
},
|
||||
}
|
||||
this.cached.set(`q${id}`, formatted)
|
||||
return formatted
|
||||
},
|
||||
}
|
||||
48
source/plugins/stackoverflow/metadata.yml
Normal file
48
source/plugins/stackoverflow/metadata.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
name: "🗨️ Stackoverflow plugin"
|
||||
cost: N/A
|
||||
categorie: social
|
||||
supports:
|
||||
- user
|
||||
- organization
|
||||
inputs:
|
||||
|
||||
# Enable or disable plugin
|
||||
plugin_stackoverflow:
|
||||
description: Stackoverflow metrics
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Stackoverflow user id
|
||||
# To obtain it, extract the identifier on your account page url
|
||||
plugin_stackoverflow_user:
|
||||
description: Stackoverflow user id
|
||||
type: number
|
||||
default: 0
|
||||
|
||||
# Sections to display
|
||||
plugin_stackoverflow_sections:
|
||||
description: Sections to display
|
||||
type: array
|
||||
format: comma-separated
|
||||
default: answers-top, questions-recent
|
||||
values:
|
||||
- answers-top # Display top answers
|
||||
- answers-recent # Display recent answers
|
||||
- questions-top # Display top questions
|
||||
- questions-recent # Display recent questions
|
||||
|
||||
# Number of entries to display per section
|
||||
plugin_stackoverflow_limit:
|
||||
description: Maximum number of entries to display per section
|
||||
type: number
|
||||
default: 2
|
||||
min: 1
|
||||
max: 30
|
||||
|
||||
# Number of lines to display per question or answer
|
||||
# Set to 0 to disable limitations
|
||||
plugin_stackoverflow_lines:
|
||||
description: Maximum number of lines to display per question or answer
|
||||
type: number
|
||||
default: 4
|
||||
min: 0
|
||||
16
source/plugins/stackoverflow/tests.yml
Normal file
16
source/plugins/stackoverflow/tests.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
- name: Stackoverflow plugin (default)
|
||||
uses: lowlighter/metrics@latest
|
||||
with:
|
||||
token: MOCKED_TOKEN
|
||||
plugin_stackoverflow: yes
|
||||
plugin_stackoverflow_user: 1
|
||||
|
||||
- name: Stackoverflow plugin (complete)
|
||||
uses: lowlighter/metrics@latest
|
||||
with:
|
||||
token: MOCKED_TOKEN
|
||||
plugin_stackoverflow: yes
|
||||
plugin_stackoverflow_user: 1
|
||||
plugin_stackoverflow_sections: answers-top, answers-recent, questions-top, questions-recent
|
||||
plugin_stackoverflow_limit: 2
|
||||
plugin_stackoverflow_lines: 4
|
||||
Reference in New Issue
Block a user