Skip to content

Instantly share code, notes, and snippets.

@tetsuya-ki
Last active December 3, 2023 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tetsuya-ki/71f7bb18a6573ed347b8feb058dd6998 to your computer and use it in GitHub Desktop.
Save tetsuya-ki/71f7bb18a6573ed347b8feb058dd6998 to your computer and use it in GitHub Desktop.
GitHubTrafficGraph2Discord
const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty("DISCORD_WEBHOOK_URL");
const OWNER = PropertiesService.getScriptProperties().getProperty("OWNER");
const REPO = PropertiesService.getScriptProperties().getProperty("REPO");
const ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("ACCESS_TOKEN");
const SPREADSHEET_ID = PropertiesService.getScriptProperties().getProperty("SPREADSHEET_ID");
const TEXT_LENGTH_MAX = 1950;
const NUMBAR = 6;
const SHEETNAME_WORK = 'WORK';
// 曜日として表示したい名前(Sun)とかにしてもOK
const weekDay = {
0: '(日)',
1: '(月)',
2: '(火)',
3: '(水)',
4: '(木)',
5: '(金)',
6: '(土)',
};
const repos = {
// 書き込み先がチャンネルの場合は、空白を記載
'repository_name_0':''
// スレッドに投稿する場合はスレッドIDを記載
'repository_name_1':'discord_thread_id_1'
// 同じスレッドに投稿することも可能(当たり前か...)
, 'repository_name_2':'discord_thread_id_2'
, 'repository_name_3':'discord_thread_id_2'
};
function _fetchTrafficByGitHubApi(repo, path){
var url = `https://api.github.com/repos/${OWNER}/${repo}/traffic/${path}`;
var options = {
headers: {
'Authorization': `token ${ACCESS_TOKEN}`
, 'Accept': 'application/vnd.github+json'
, 'X-GitHub-Api-Version': '2022-11-28'
}
};
var response = UrlFetchApp.fetch(url, options);
var json = response.getContentText();
Logger.log(json);
return JSON.parse(json);
}gi
function getTrafficViewsAndWriteWork(repo) {
var parsed_data = _fetchTrafficByGitHubApi(repo, 'views');
var checkDay = new Date();
checkDay.setDate(checkDay.getDate() - 14);
checkDay = new Date(checkDay.toDateString());
var checkEndDay = new Date();
checkEndDay.setDate(checkEndDay.getDate() - 1);
checkEndDay = new Date(checkEndDay.toDateString());
var headers = ['timestamp', 'count', 'uniques'];
var data = [];
var spreadsheet_data = [];
for (var i = 0; i < parsed_data.views.length; i++) {
const count = parsed_data.views[i].count;
const uniques = parsed_data.views[i].uniques;
const dt = new Date(parsed_data.views[i].timestamp);
var targetDay = new Date(dt.toDateString());
if (checkEndDay.valueOf() <= targetDay.valueOf()){
break;
}
// GitHubではデータなしの場合、その日付自体が存在しないため、補正する
if (targetDay.valueOf() !== checkDay.valueOf()) {
while(targetDay.valueOf() !== checkDay.valueOf()){
var row = [];
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(0);
row.push(0);
data.push(row);
spreadsheet_data.push([0, 0]);
checkDay.setDate(checkDay.getDate() + 1);
if (checkEndDay.valueOf() <= checkDay.valueOf()){
break;
}
}
}
var row = [];
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(count);
row.push(uniques);
data.push(row);
spreadsheet_data.push([count, uniques]);
checkDay.setDate(checkDay.getDate() + 1);
}
// 日付が足りない場合の処理
for (i = data.length; i <= 14; i++){
var row = [];
var checkDay = new Date();
checkDay.setDate(checkDay.getDate() - 14 + i);
checkDay = new Date(checkDay.toDateString());
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(0);
row.push(0);
data.push(row);
spreadsheet_data.push([0, 0]);
}
Logger.log('書き込みやる2');
Logger.log(headers);
Logger.log(data);
_writeSpreadsheetWork(SPREADSHEET_ID, headers, data);
_writeRepoDays(SPREADSHEET_ID, repo, false, spreadsheet_data);
return {count:parsed_data.count, uniques:parsed_data.uniques};
}
function getTrafficClonesAndWriteWork(repo) {
var parsed_data = _fetchTrafficByGitHubApi(repo, 'clones');
var checkDay = new Date();
checkDay.setDate(checkDay.getDate() - 14);
checkDay = new Date(checkDay.toDateString());
var checkEndDay = new Date();
checkEndDay.setDate(checkEndDay.getDate() - 1);
checkEndDay = new Date(checkEndDay.toDateString());
var headers = ['timestamp', 'count', 'uniques'];
var data = [];
for (var i = 0; i < parsed_data.clones.length; i++) {
const count = parsed_data.clones[i].count;
const uniques = parsed_data.clones[i].uniques;
const dt = new Date(parsed_data.clones[i].timestamp);
var targetDay = new Date(dt.toDateString());
if (checkEndDay.valueOf() <= targetDay.valueOf()){
break;
}
// GitHubではデータなしの場合、その日付自体が存在しないため、補正する
if (targetDay.valueOf() !== checkDay.valueOf()) {
while(targetDay.valueOf() !== checkDay.valueOf()){
var row = [];
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(0);
row.push(0);
data.push(row);
checkDay.setDate(checkDay.getDate() + 1);
if (checkEndDay.valueOf() <= checkDay.valueOf()){
break;
}
}
}
var row = [];
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(count);
row.push(uniques);
data.push(row);
checkDay.setDate(checkDay.getDate() + 1);
}
// 日付が足りない場合の処理
for (i = data.length; i <= 14; i++){
var row = [];
var checkDay = new Date();
checkDay.setDate(checkDay.getDate() - 14 + i);
checkDay = new Date(checkDay.toDateString());
// 日付準備
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const date_str = `${month}/${day}${weekD}`;
row.push(date_str);
row.push(0);
row.push(0);
data.push(row);
}
Logger.log('書き込みやる');
Logger.log(headers);
Logger.log(data);
_writeSpreadsheetWork(SPREADSHEET_ID, headers, data);
_writeRepoDays(SPREADSHEET_ID, repo, true, data);
return {count:parsed_data.count, uniques:parsed_data.uniques};
}
function getTrafficPopularReferrers(repo) {
const path = 'popular/referrers';
var parsed_data = _fetchTrafficByGitHubApi(repo, path);
var text = `### ${path}\n`;
var length = parsed_data.length;
if (parsed_data.length > NUMBAR){
length = NUMBAR;
} else if (parsed_data.length == 0) {
text = '';
}
for (var i = 0; i < length; i++) {
text += parsed_data[i].referrer;
text += `: count(${parsed_data[i].count}), uniques(${parsed_data[i].uniques})\n`;
}
return text;
}
function getTrafficPopularPaths(repo) {
const path = 'popular/paths';
var parsed_data = _fetchTrafficByGitHubApi(repo, path);
var text = `### ${path}\n`;
var length = parsed_data.length;
if (parsed_data.length > NUMBAR){
length = NUMBAR;
} else if (parsed_data.length == 0) {
text = '';
}
for (var i = 0; i < length; i++) {
text += parsed_data[i].path;
text += `: count(${parsed_data[i].count}), uniques(${parsed_data[i].uniques})\n`;
}
return text;
}
function exportChart(repo, webhook_url) {
var text = `## traffic: ${repo}\n`
// clones
var clonesData = getTrafficClonesAndWriteWork(repo);
if (clonesData.uniques > 0){
var chartClones = _createChartByWORK(SPREADSHEET_ID, `${repo} - clones`);
var imageDataClones = chartClones.getAs('image/png').copyBlob();
}
// views
var viewsData = getTrafficViewsAndWriteWork(repo);
if (viewsData.uniques > 1){
var chartViews = _createChartByWORK(SPREADSHEET_ID, `${repo} - views`);
var imageDataViews = chartViews.getAs('image/png').copyBlob();
}
// others
var referrersText = getTrafficPopularReferrers(repo);
var pathsText = getTrafficPopularPaths(repo);
// summary書き込み
_writeRepoSummary(SPREADSHEET_ID, repo, [clonesData.count, clonesData.uniques, viewsData.count, viewsData.uniques]);
// 投稿文章作成
text += `clones-> 総数:${clonesData.count}, ユニーク数:${clonesData.uniques}\n`;
text += `views-> 総数:${viewsData.count}, ユニーク数:${viewsData.uniques}\n`;
text += referrersText;
text += pathsText;
resultText = text;
if(text.length>TEXT_LENGTH_MAX){
resultText = text.slice(0, TEXT_LENGTH_MAX);
resultText += '\n...(省略)...';
}
// Discord投稿
var payload = {
'content' : resultText
};
if (viewsData.uniques > 1 && clonesData.uniques > 0) {
payload['file1'] = imageDataClones;
payload['file2'] = imageDataViews;
} else if (viewsData.uniques > 1 && clonesData.uniques == 0) {
payload['file1'] = imageDataViews;
} else if (viewsData.uniques == 1 && clonesData.uniques > 0) {
payload['file1'] = imageDataClones;
}
var params = {
method: "post",
payload: payload,
muteHttpExceptions: true
};
UrlFetchApp.fetch(webhook_url, params);
}
function exportChartREPO() {
exportChart(REPO, WEBHOOK_URL);
}
function exportChartAll() {
if (repos == undefined || repos.length == 0) {
Logger.log('データなし。終了');
return;
}
for (let [key, value] of Object.entries(repos)) {
if (value !== '') {
exportChart(key, `${WEBHOOK_URL}?thread_id=${value}`);
} else {
exportChart(key, `${WEBHOOK_URL}`);
}
Utilities.sleep(500);// 0.5[sec]お休み
}
}
function _writeSpreadsheetWork(spreadsheet_id, headers, data){
const activeSpreadSheet = SpreadsheetApp.openById(spreadsheet_id);
const sheet = activeSpreadSheet.getSheetByName(SHEETNAME_WORK);
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(2, 1, data.length, 3).setValues(data);
}
function _createChartByWORK(spreadsheet_id, name) {
const activeSpreadSheet = SpreadsheetApp.openById(spreadsheet_id);
const sheet = activeSpreadSheet.getSheetByName(SHEETNAME_WORK);
var chart = sheet.newChart()
.setChartType(Charts.ChartType.LINE)
.addRange(sheet.getRange("A1:C17"))
.setPosition(5, 5, 0, 0)
.setNumHeaders(1)
.setOption('title', `Traffic: ${name}`)
.setOption('hAxis', {title: 'Date'})
.setOption('vAxes', {0: {title: 'count', textStyle: {color: 'blue'}, gridlines: {color: 'black'}, minorGridlines: {color: 'grey'}, visibleInLegend: true}
,1: {title: 'uniques', textStyle: {color: 'red'}, gridlines: {color: 'black'}, minorGridlines: {color: 'grey'}, visibleInLegend: true}})
.setOption('series', {0: {targetAxisIndex: 0, dataLabel: 'value', dataLabelPlacement: 'center'}
, 1: {targetAxisIndex: 1, dataLabel: 'value', dataLabelPlacement: 'center'}})
.build();
// sheet.insertChart(chart);
return chart;
}
function _writeRepoSummary(spreadsheet_id, repo_name, data_list){
const activeSpreadSheet = SpreadsheetApp.openById(spreadsheet_id);
const sheetName = `${repo_name}-summary`;
let sheet = activeSpreadSheet.getSheetByName(sheetName);
// 日付準備
const checkDay = new Date();
const month = (checkDay.getMonth() + 1).toString().padStart(2, '0');
const day = checkDay.getDate().toString().padStart(2, '0');
const weekD = weekDay[checkDay.getDay()];
const hour = checkDay.getHours().toString().padStart(2, '0');
const minute = checkDay.getMinutes().toString().padStart(2, '0');
const date_str = `${month}/${day}${weekD} ${hour}:${minute}`;
data_list.unshift(date_str);
// 新規作成の場合
if(sheet == null){
sheet = activeSpreadSheet.insertSheet(sheetName);
const headers = ['取得日','clone-count','clone-uniques','view-count','view-uniques'];
const write_list = [headers, data_list];
sheet.getRange(1,1,write_list.length,headers.length).setValues(write_list);
} else {
// すでに存在する場合は末尾に記載
const start_row = sheet.getLastRow()+1;
sheet.getRange(start_row,1,1,data_list.length).setValues([data_list]);
}
}
function _writeRepoDays(spreadsheet_id, repo_name, clone_flg, data_list){
const activeSpreadSheet = SpreadsheetApp.openById(spreadsheet_id);
const sheetName = `${repo_name}-days`;
const COLUMN_NUM = 4;
let sheet = activeSpreadSheet.getSheetByName(sheetName);
// 新規作成の場合
if(sheet == null){
sheet = activeSpreadSheet.insertSheet(sheetName);
if(clone_flg){
const headers = ['日付','clone-count','clone-uniques','view-count','view-uniques'];
// ヘッダーの記載
sheet.getRange(1,1,1,headers.length).setValues([headers]);
// データ記載
data_list = data_list.slice(0, 14);
sheet.getRange(2,1,data_list.length,data_list[0].length).setValues(data_list);
} else {
// データ記載
data_list = data_list.slice(-8);
data_list = data_list.slice(0, 7);
sheet.getRange(2,COLUMN_NUM,data_list.length,data_list[0].length).setValues(data_list);
}
} else {
// すでに存在する場合は末尾に記載
if(clone_flg){
let write_list = data_list.slice(-8);
write_list = write_list.slice(0, 7);
let lastRow = sheet.getRange(sheet.getMaxRows(), 1).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
const finalRow = sheet.getRange(sheet.getMaxRows(), 1).getRow();
const finalRowData = sheet.getRange(finalRow, 1).getValue();
if (finalRowData !== ''){
lastRow = finalRow;
}
// データ記載
sheet.getRange(lastRow+1, 1, write_list.length, write_list[0].length).setValues(write_list);
} else {
let lastRow = sheet.getRange(sheet.getMaxRows(), COLUMN_NUM).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
const finalRow = sheet.getRange(sheet.getMaxRows(), COLUMN_NUM).getRow();
const finalRowData = sheet.getRange(finalRow, 1).getValue();
if (finalRowData !== ''){
lastRow = finalRow;
}
if (lastRow === 1) {
data_list = data_list.slice(0, 14);
sheet.getRange(lastRow+1, COLUMN_NUM, data_list.length, data_list[0].length).setValues(data_list);
} else {
data_list = data_list.slice(-8);
data_list = data_list.slice(0, 7);
// データ記載
sheet.getRange(lastRow+1, COLUMN_NUM, data_list.length, data_list[0].length).setValues(data_list);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment