안녕하세요 별깡솔입니다 : )
별깡솔 블로그를 방문하신 모든 분들 진심으로 감사드립니다
인스타그램 피드 Access Token & API 없이 사용하기
카페24 앱스토어에 인스타그램 위젯 기능을 사용한 경험이 있어서
티스토리에도 적용하고 싶었습니다
티스토리에서 인스타그램 위젯 기능을 제공하지 않기 때문에
인스타그램 피드를 가져와 사용하기가 초보자 입장에선 매우 어려웠습니다
스냅 위젯을 사용하는 방법이 있지만 내가 원하는 레이아웃을 만들기 어렵고
페이스북 개발자 페이지에서 앱을 만들어 토큰을 얻어 사용하는 방법도 어렵습니다
페이스북에 인스타 연결부터 시작해서 해야할 것이 많습니다
그리고 힘들게 얻은 토큰을 장기 토큰으로 만들고 그 토큰마저 60일 제한이라 토큰을 관리하기가 짜증납니다
인스타그램 아이디만 가지고 사용하는 방법에 대해서 알아보겠습니다
소스 코드 수정 전에 반드시 스킨 백업 하시길 바랍니다
instafeed.js 파일 연결 후 html, css 작성입니다
html과 css에 대해 잘 모르신다면 제가 올린 소스코드를 해당 위치에 붙이시길 바랍니다
https://github.com/jsanahuja/InstagramFeed에서
InstagramFeed-master.zip 파일을 받습니다
압축을 풀면 src 폴더 안에 js 파일이 있습니다 (instagramFeed.min.js - dist 폴더)
파일을 받습니다
InstagramFeed.js
InstagramFeed.min.js
js 소스 내용입니다
/*
* InstagramFeed
*
* @version 1.4.0
*
* @author Javier Sanahuja Liebana <bannss1@gmail.com>
* @contributor csanahuja <csanahuja@gmail.com>
*
* https://github.com/jsanahuja/InstagramFeed
*
*/
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.InstagramFeed = factory();
}
}(this, function() {
var defaults = {
'host': "https://www.instagram.com/",
'username': '',
'tag': '',
'container': '',
'display_profile': true,
'display_biography': true,
'display_gallery': true,
'display_igtv': false,
'callback': null,
'styling': true,
'items': 8,
'items_per_row': 4,
'margin': 0.5,
'image_size': 640,
'lazy_load': false,
'on_error': console.error
};
var image_sizes = {
"150": 0,
"240": 1,
"320": 2,
"480": 3,
"640": 4
};
var escape_map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '='
};
function escape_string(str){
return str.replace(/[&<>"'`=\/]/g, function (char) {
return escape_map[char];
});
}
return function(opts) {
this.options = Object.assign({}, defaults);
this.options = Object.assign(this.options, opts);
this.is_tag = this.options.username == "";
this.valid = true;
if (this.options.username == "" && this.options.tag == "") {
this.options.on_error("InstagramFeed: Error, no username or tag defined.", 1);
this.valid = false;
}
if (typeof this.options.get_data !== "undefined") {
console.warn("InstagramFeed: options.get_data is deprecated, options.callback is always called if defined");
}
if (this.options.callback == null && this.options.container == "") {
this.options.on_error("InstagramFeed: Error, neither container found nor callback defined.", 2);
this.valid = false;
}
this.get = function(callback) {
var url = this.is_tag ? this.options.host + "explore/tags/" + this.options.tag + "/" : this.options.host + this.options.username + "/",
xhr = new XMLHttpRequest();
var _this = this;
xhr.onload = function(e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try{
var data = xhr.responseText.split("window._sharedData = ")[1].split("<\/script>")[0];
}catch(error){
_this.options.on_error("InstagramFeed: It looks like the profile you are trying to fetch is age restricted. See https://github.com/jsanahuja/InstagramFeed/issues/26", 3);
return;
}
data = JSON.parse(data.substr(0, data.length - 1));
data = data.entry_data.ProfilePage || data.entry_data.TagPage;
if(typeof data === "undefined"){
_this.options.on_error("InstagramFeed: It looks like YOUR network has been temporary banned because of too many requests. See https://github.com/jsanahuja/jquery.instagramFeed/issues/25", 4);
return;
}
data = data[0].graphql.user || data[0].graphql.hashtag;
callback(data, _this);
} else {
_this.options.on_error("InstagramFeed: Unable to fetch the given user/tag. Instagram responded with the status code: " + xhr.statusText, 5);
}
}
};
xhr.open("GET", url, true);
xhr.send();
};
this.parse_caption = function(igobj, data) {
if (
typeof igobj.node.edge_media_to_caption.edges[0] !== "undefined" &&
typeof igobj.node.edge_media_to_caption.edges[0].node !== "undefined" &&
typeof igobj.node.edge_media_to_caption.edges[0].node.text !== "undefined" &&
igobj.node.edge_media_to_caption.edges[0].node.text !== null
) {
return igobj.node.edge_media_to_caption.edges[0].node.text;
}
if (
typeof igobj.node.title !== "undefined" &&
igobj.node.title !== null &&
igobj.node.title.length != 0
) {
return igobj.node.title;
}
if (
typeof igobj.node.accessibility_caption !== "undefined" &&
igobj.node.accessibility_caption !== null &&
igobj.node.accessibility_caption.length != 0
) {
return igobj.node.accessibility_caption;
}
return (this.is_tag ? data.name : data.username) + " image ";
}
this.display = function(data) {
// Styling
if (this.options.styling) {
var width = (100 - this.options.margin * 2 * this.options.items_per_row) / this.options.items_per_row;
var styles = {
'profile_container': " style='text-align:center;'",
'profile_image': " style='border-radius:10em;width:15%;max-width:125px;min-width:50px;'",
'profile_name': " style='font-size:1.2em;'",
'profile_biography': " style='font-size:1em;'",
'gallery_image': " style='margin:" + this.options.margin + "% " + this.options.margin + "%;width:" + width + "%;float:left;'"
};
} else {
var styles = {
'profile_container': "",
'profile_image': "",
'profile_name': "",
'profile_biography': "",
'gallery_image': ""
};
}
// Profile
var html = "";
if (this.options.display_profile) {
html += "<div class='instagram_profile'" + styles.profile_container + ">";
html += "<img class='instagram_profile_image'" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + data.profile_pic_url + "' alt='" + (this.is_tag ? data.name + " tag pic" : data.username + " profile pic") + " profile pic'" + styles.profile_image + " />";
if (this.is_tag)
html += "<p class='instagram_tag'" + styles.profile_name + "><a href='https://www.instagram.com/explore/tags/" + this.options.tag + "' rel='noopener' target='_blank'>#" + this.options.tag + "</a></p>";
else
html += "<p class='instagram_username'" + styles.profile_name + ">@" + data.full_name + " (<a href='https://www.instagram.com/" + this.options.username + "' rel='noopener' target='_blank'>@" + this.options.username + "</a>)</p>";
if (!this.is_tag && this.options.display_biography)
html += "<p class='instagram_biography'" + styles.profile_biography + ">" + data.biography + "</p>";
html += "</div>";
}
// Gallery
if (this.options.display_gallery) {
var image_index = typeof image_sizes[this.options.image_size] !== "undefined" ? image_sizes[this.options.image_size] : image_sizes[640];
if (typeof data.is_private !== "undefined" && data.is_private === true) {
html += "<p class='instagram_private'><strong>This profile is private</strong></p>";
} else {
var imgs = (data.edge_owner_to_timeline_media || data.edge_hashtag_to_media).edges;
max = (imgs.length > this.options.items) ? this.options.items : imgs.length;
html += "<div class='instagram_gallery'>";
for (var i = 0; i < max; i++) {
var url = "https://www.instagram.com/p/" + imgs[i].node.shortcode,
image, type_resource,
caption = escape_string(this.parse_caption(imgs[i], data));
switch (imgs[i].node.__typename) {
case "GraphSidecar":
type_resource = "sidecar"
image = imgs[i].node.thumbnail_resources[image_index].src;
break;
case "GraphVideo":
type_resource = "video";
image = imgs[i].node.thumbnail_src
break;
default:
type_resource = "image";
image = imgs[i].node.thumbnail_resources[image_index].src;
}
if (this.is_tag) data.username = '';
html += "<a href='" + url + "' class='instagram-" + type_resource + "' title='" + caption + "' rel='noopener' target='_blank'>";
html += "<img" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + image + "' alt='" + caption + "'" + styles.gallery_image + " />";
html += "</a>";
}
html += "</div>";
}
}
// IGTV
if (this.options.display_igtv && typeof data.edge_felix_video_timeline !== "undefined") {
var igtv = data.edge_felix_video_timeline.edges,
max = (igtv.length > this.options.items) ? this.options.items : igtv.length
if (igtv.length > 0) {
html += "<div class='instagram_igtv'>";
for (var i = 0; i < max; i++) {
var url = "https://www.instagram.com/p/" + igtv[i].node.shortcode,
caption = this.parse_caption(igtv[i], data);
html += "<a href='" + url + "' rel='noopener' title='" + caption + "' target='_blank'>";
html += "<img" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + igtv[i].node.thumbnail_src + "' alt='" + caption + "'" + styles.gallery_image + " />";
html += "</a>";
}
html += "</div>";
}
}
this.options.container.innerHTML = html;
};
this.run = function() {
this.get(function(data, instance) {
if(instance.options.container != ""){
instance.display(data);
}
if(typeof instance.options.callback === "function"){
instance.options.callback(data);
}
});
};
if (this.valid) {
this.run();
}
};
}));
티스토리 블로그 관리 - 스킨 편집 - html 편집 - 파일업로드에 js 파일을 업로드 합니다
html 편집에서 js 파일을 연결합니다
<head> 와 <head> 사이에 아래 코드를 넣습니다
<script src="./images/instagramFeed.js"></script>
코드 삽입 위치만 참고하세요
html
5가지 Example 중에서 마음에 드는 레이아웃을 선택합니다
Example 1
인스타그램 피드를 표시할 곳에 아래 코드를 넣습니다
<div id="instagram-feed"></div>
<script>
(function(){
new InstagramFeed({
'username': '인스타아이디',
'container': document.getElementById("instagram-feed"),
'display_profile': true,
'display_biography': true,
'display_gallery': true,
'callback': null,
'styling': true,
'items': 8,
'items_per_row': 4,
'margin': 1,
'lazy_load': true,
'on_error': console.error
});
})();
</script>
Example 2
<div id="instagram-feed"></div>
<script>
(function(){
new InstagramFeed({
'username': '인스타아이디',
'container': document.getElementById("instagram-feed"),
'display_profile': false,
'display_biography': false,
'display_gallery': true,
'callback': null,
'styling': true,
'items': 8,
'items_per_row': 4,
'margin': 1
});
})();
</script>
Example 3
<div id="instagram-feed"></div>
<script>
(function(){
new InstagramFeed({
'username': '인스타아이디',
'container': document.getElementById("instagram-feed"),
'display_profile': false,
'display_biography': false,
'display_gallery': true,
'callback': null,
'styling': true,
'items': 12,
'items_per_row': 6,
'margin': 0.25
});
})();
</script>
Example 4
<div id="instagram-feed"></div>
<script>
(function(){
new InstagramFeed({
'tag': '태그',
'container': document.getElementById("instagram-feed"),
'display_profile': true,
'display_gallery': true,
'items': 15,
'items_per_row': 5,
'margin': 0.5
});
})();
</script>
Example 5
<div id="instagram-feed"></div>
<script>
(function(){
new InstagramFeed({
'username': '인스타아이디',
'container': document.getElementById("instagram-feed"),
'display_profile': false,
'display_biography': false,
'display_gallery': false,
'display_igtv': true,
'callback': null,
'styling': true,
'items': 8,
'items_per_row': 4,
'margin': 1
});
})();
</script>
css
html 코드를 수정해서 붙여 넣으셨다면 피드 내용이 보일 것입니다
'styling' 디폴트 값이 true 입니다
'styling': false, 로 하지 않는다면 Example과 같은 레이아웃으로 표시됩니다
'items': 숫자, 원하는 출력 개수를 입력하세요
'items_per_row': 한 줄에 표시할 개수를 입력하세요
옵션을 참고하세요
별깡솔 스타일
제가 사용중인 레이아웃을 참고하실 분들은 아래 코드를 사용하세요
instagramFeed.js 소스 코드가 다릅니다
instagramFeed.js
js
/*
* InstagramFeed
*
* @version 1.4.0
*
* @author Javier Sanahuja Liebana <bannss1@gmail.com>
* @contributor csanahuja <csanahuja@gmail.com>
*
* https://github.com/jsanahuja/InstagramFeed
*
*/
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.InstagramFeed = factory();
}
}(this, function() {
var defaults = {
'host': "https://www.instagram.com/",
'username': '',
'tag': '',
'container': '',
'display_profile': true,
'display_biography': true,
'display_gallery': true,
'display_igtv': false,
'callback': null,
'styling': true,
'items': 8,
'items_per_row': 4,
'margin': 0.5,
'image_size': 640,
'lazy_load': false,
'on_error': console.error
};
var image_sizes = {
"150": 0,
"240": 1,
"320": 2,
"480": 3,
"640": 4
};
var escape_map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '='
};
function escape_string(str){
return str.replace(/[&<>"'`=\/]/g, function (char) {
return escape_map[char];
});
}
return function(opts) {
this.options = Object.assign({}, defaults);
this.options = Object.assign(this.options, opts);
this.is_tag = this.options.username == "";
this.valid = true;
if (this.options.username == "" && this.options.tag == "") {
this.options.on_error("InstagramFeed: Error, no username or tag defined.", 1);
this.valid = false;
}
if (typeof this.options.get_data !== "undefined") {
console.warn("InstagramFeed: options.get_data is deprecated, options.callback is always called if defined");
}
if (this.options.callback == null && this.options.container == "") {
this.options.on_error("InstagramFeed: Error, neither container found nor callback defined.", 2);
this.valid = false;
}
this.get = function(callback) {
var url = this.is_tag ? this.options.host + "explore/tags/" + this.options.tag + "/" : this.options.host + this.options.username + "/",
xhr = new XMLHttpRequest();
var _this = this;
xhr.onload = function(e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try{
var data = xhr.responseText.split("window._sharedData = ")[1].split("<\/script>")[0];
}catch(error){
_this.options.on_error("InstagramFeed: It looks like the profile you are trying to fetch is age restricted. See https://github.com/jsanahuja/InstagramFeed/issues/26", 3);
return;
}
data = JSON.parse(data.substr(0, data.length - 1));
data = data.entry_data.ProfilePage || data.entry_data.TagPage;
if(typeof data === "undefined"){
_this.options.on_error("InstagramFeed: It looks like YOUR network has been temporary banned because of too many requests. See https://github.com/jsanahuja/jquery.instagramFeed/issues/25", 4);
return;
}
data = data[0].graphql.user || data[0].graphql.hashtag;
callback(data, _this);
} else {
_this.options.on_error("InstagramFeed: Unable to fetch the given user/tag. Instagram responded with the status code: " + xhr.statusText, 5);
}
}
};
xhr.open("GET", url, true);
xhr.send();
};
this.parse_caption = function(igobj, data) {
if (
typeof igobj.node.edge_media_to_caption.edges[0] !== "undefined" &&
typeof igobj.node.edge_media_to_caption.edges[0].node !== "undefined" &&
typeof igobj.node.edge_media_to_caption.edges[0].node.text !== "undefined" &&
igobj.node.edge_media_to_caption.edges[0].node.text !== null
) {
return igobj.node.edge_media_to_caption.edges[0].node.text;
}
if (
typeof igobj.node.title !== "undefined" &&
igobj.node.title !== null &&
igobj.node.title.length != 0
) {
return igobj.node.title;
}
if (
typeof igobj.node.accessibility_caption !== "undefined" &&
igobj.node.accessibility_caption !== null &&
igobj.node.accessibility_caption.length != 0
) {
return igobj.node.accessibility_caption;
}
return (this.is_tag ? data.name : data.username) + " image ";
}
this.display = function(data) {
// Styling
if (this.options.styling) {
var width = (100 - this.options.margin * 2 * this.options.items_per_row) / this.options.items_per_row;
var styles = {
'profile_container': " style='text-align:center;'",
'profile_image': " style='border-radius:10em;width:15%;max-width:125px;min-width:50px;'",
'profile_name': " style='font-size:1.2em;'",
'profile_biography': " style='font-size:1em;'",
'gallery_image': " style='margin:" + this.options.margin + "% " + this.options.margin + "%;width:" + width + "%;float:left;'"
};
} else {
var styles = {
'profile_container': "",
'profile_image': "",
'profile_name': "",
'profile_biography': "",
'gallery_image': ""
};
}
// Profile
var html = "";
if (this.options.display_profile) {
html += "<div class='instagram_profile'" + styles.profile_container + ">";
html += "<img class='instagram_profile_image'" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + data.profile_pic_url + "' alt='" + (this.is_tag ? data.name + " tag pic" : data.username + " profile pic") + " profile pic'" + styles.profile_image + " />";
if (this.is_tag)
html += "<p class='instagram_tag'" + styles.profile_name + "><a href='https://www.instagram.com/explore/tags/" + this.options.tag + "' rel='noopener' target='_blank'>#" + this.options.tag + "</a></p>";
else
html += "<p class='instagram_username'" + styles.profile_name + ">@" + data.full_name + " (<a href='https://www.instagram.com/" + this.options.username + "' rel='noopener' target='_blank'>@" + this.options.username + "</a>)</p>";
if (!this.is_tag && this.options.display_biography)
html += "<p class='instagram_biography'" + styles.profile_biography + ">" + data.biography + "</p>";
html += "</div>";
}
// Gallery
if (this.options.display_gallery) {
var image_index = typeof image_sizes[this.options.image_size] !== "undefined" ? image_sizes[this.options.image_size] : image_sizes[640];
if (typeof data.is_private !== "undefined" && data.is_private === true) {
html += "<p class='instagram_private'><strong>This profile is private</strong></p>";
} else {
var imgs = (data.edge_owner_to_timeline_media || data.edge_hashtag_to_media).edges;
max = (imgs.length > this.options.items) ? this.options.items : imgs.length;
html += "<ul class='instagram_gallery'>";
for (var i = 0; i < max; i++) {
var url = "https://www.instagram.com/p/" + imgs[i].node.shortcode,
image, type_resource,
caption = escape_string(this.parse_caption(imgs[i], data));
switch (imgs[i].node.__typename) {
case "GraphSidecar":
type_resource = "sidecar"
image = imgs[i].node.thumbnail_resources[image_index].src;
break;
case "GraphVideo":
type_resource = "video";
image = imgs[i].node.thumbnail_src
break;
default:
type_resource = "image";
image = imgs[i].node.thumbnail_resources[image_index].src;
}
if (this.is_tag) data.username = '';
html += "<li><a href='" + url + "' class='instagram-" + type_resource + "' title='" + caption + "' rel='noopener' target='_blank'>";
html += "<img" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + image + "' alt='" + caption + "'" + styles.gallery_image + " />";
html += "<div class='caption-overlay'><p class='instagram-caption'>" + caption + "</p></div>";
html += "</a></li>";
}
html += "</ul>";
}
}
// IGTV
if (this.options.display_igtv && typeof data.edge_felix_video_timeline !== "undefined") {
var igtv = data.edge_felix_video_timeline.edges,
max = (igtv.length > this.options.items) ? this.options.items : igtv.length
if (igtv.length > 0) {
html += "<div class='instagram_igtv'>";
for (var i = 0; i < max; i++) {
var url = "https://www.instagram.com/p/" + igtv[i].node.shortcode,
caption = this.parse_caption(igtv[i], data);
html += "<a href='" + url + "' rel='noopener' title='" + caption + "' target='_blank'>";
html += "<img" + (this.options.lazy_load ? " loading='lazy'" : '') + " src='" + igtv[i].node.thumbnail_src + "' alt='" + caption + "'" + styles.gallery_image + " />";
html += "<p class='instagram-caption'>" + caption + "</p>";
html += "</a>";
}
html += "</div>";
}
}
this.options.container.innerHTML = html;
};
this.run = function() {
this.get(function(data, instance) {
if(instance.options.container != ""){
instance.display(data);
}
if(typeof instance.options.callback === "function"){
instance.options.callback(data);
}
});
};
if (this.valid) {
this.run();
}
};
}));
html ( footer에 위치합니다)
<div class="footer-instagram">
<h3>instagram feed</h3>
<div class="instagram-box">
<div id="instafeed"></div>
</div>
</div>
<!-- instafeed -->
<script>
(function() {
new InstagramFeed({
'username': '인스타아이디',
'container': document.getElementById("instafeed"),
'display_profile': false,
'display_biography': false,
'display_gallery': true,
'display_igtv': false,
'callback': null,
'styling': false,
'items': 10, // 표시 개수
'lazy_load': true,
'on_error': console.error
});
})();
</script>
css ( 'font-awesome'을 사용합니다. 사용 방법을 아래 링크를 참고하세요 )
2020/10/20 - [티스토리/티스토리 꾸미기] - Web Font XEIcon & Font-awesome 사용하기
#footer .footer-container {font-size: 0;}
/* ----- pc width size ----- */
#footer .footer-container .footer-instagram {
width: 1000px; /* width 값을 조절해주세요 */
margin: 0 auto;
}
#footer .footer-container .footer-instagram h3 {
font-size: 18px; // 글자 크기
text-transform: uppercase;
font-weight: 400; // 글자 굵기
letter-spacing: 2px;// 글자 자간
border-bottom: 3px solid;
padding-bottom: 20px;
}
.instagram-box {}
#instafeed {
position: relative;
}
#instafeed .instagram_gallery {
list-style: none;
padding: 5px 0 20px;
margin: 0;
}
#instafeed .instagram_gallery li {
display: inline-block;
width: 20%;
overflow: hidden;
}
#instafeed .instagram_gallery li a {
display: block;
position: relative;
}
#instafeed .instagram_gallery li a:after {
content: '\f16d';
font-family: 'Font Awesome 5 Brands';
font-weight: 400;
position: absolute;
right: 10px;
bottom: 10px;
color: #fff;
font-size: 22px;
transition: 0.3s;
}
#instafeed .instagram_gallery li a:hover:after {
opacity: 0;
}
#instafeed .instagram_gallery li a:hover .instagram-caption {
opacity: 1
}
#instafeed .instagram_gallery li a:hover > img {
filter: blur(1px);
}
#instafeed .instagram_gallery li a:hover > .caption-overlay {
background-color: rgba(0, 0, 0, 0.3);
}
#instafeed .instagram_gallery li a img {
width: 100%;
padding: 2px;
border-radius: 7px;
}
#instafeed .instagram_gallery li a .caption-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transition: 0.3s ease;
}
#instafeed .instagram_gallery li a .instagram-caption {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
color: #fff;
opacity: 0;
}
/* ----- media query - tablet ----- */
@media screen and (max-width: 1060px) {
#footer .footer-container .footer-instagram {
width: 100%;
padding: 20px;
}
#instafeed .instagram_gallery li {
width: 33%;
}
}
/* ----- media query - mobile ----- */
@media only screen and (max-width:768px) {
#instafeed .instagram_gallery li {
width: 49.999%;
}
}
나쁜 의도로 악용하는 일이 절대적으로 없어야 합니다
해당 포스트가 문제 된다면 댓글 남겨주세요
똑같이 따라했는데 작동 안 하는 분들도 댓글 남겨주세요
제 인스타그램 피드는 수지 인스타입니다
단지 갤러리 예시 용도입니다
본 포스트 javascript 출처는 https://github.com/jsanahuja/InstagramFeed 입니다
오픈 소스 코드 저장소 무상 서비스입니다
별깡솔 블로그를 방문하신 모든 분들 다시 한번 진심으로 감사드립니다
구독과 좋아요 감사합니다 : )