디스코드 봇 만들기#24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)
목차13개 항목
안녕하세요! Mojangcam입니다 😎
지난 #23편에서는 JSON을 버리고 aiosqlite로 봇의 데이터 계층을 제대로 다시 깔았죠. 오늘은 조금 결이 다른 글이에요. 새 기능을 만드는 게 아니라, 곧 우리 봇이 고장 날 수도 있는 변경을 미리 막는 글입니다 🛡️
2026년 8월 12일, 디스코드 개발자 체인지로그에 Breaking Change 딱지가 붙은 공지가 하나 올라왔어요. 제목이 Channel Obfuscation for Users and Bots, 우리말로 하면 채널 난독화입니다. 한 줄로 요약하면 이래요.
2026년 11월 16일부터, 봇은 VIEW_CHANNEL 권한이 없는 채널을 제대로 못 봅니다.
지금까지 봇은 자기가 못 들어가는 채널이어도 이름·주제·권한을 전부 받아볼 수 있었어요. 그게 막히는 거예요. 국내 한글 자료가 아직 거의 없어서, 오늘 제대로 정리해볼게요!
이번 편에서 배울 것
- 채널 난독화가 정확히 뭘 바꾸는지 (게이트웨이 vs HTTP)
- 내 봇이 영향을 받는지 3초 만에 판단하는 기준
- 개발자 포털에서 지금 미리 켜서 테스트하는 법
- 안 보이는 채널을 안전하게 판별하는 파이썬 코드
- 채널 목록 점검 명령어 /채널점검 만들기
🧠 왜 이런 변경을 하나요?
디스코드의 설명은 명확해요. 사용자가 접근할 수 없는 채널은 그 사용자에게 보여서도 안 된다는 프라이버시 원칙이에요. 지금까지는 권한이 없어도 채널 이름이나 구조는 클라이언트로 다 내려왔거든요.
이 규칙이 사용자에게 적용되니, 봇도 같은 규칙을 따르게 된 거예요. 봇도 결국 하나의 유저니까요 🤖
🧩 정확히 뭐가 달라지나요?
여기가 제일 헷갈리는 부분이에요. 게이트웨이와 HTTP API의 동작이 다릅니다. 이 비대칭이 오늘 글의 핵심이에요.
| 구분 | 게이트웨이 (Gateway) | HTTP API |
|---|---|---|
| 못 보는 채널 | 계속 옵니다 (가려진 상태로) | 응답에서 아예 빠집니다 |
| 채널 이름 | ___hidden___ 으로 바뀜 | 해당 없음 |
| 플래그 | CHANNEL_OBFUSCATED (1 << 17) 부여 | 이 플래그를 절대 안 붙임 |
| 미리 테스트 | 가능 (개발자 포털 토글) | 불가능 — 11/16부터 바로 적용 |
하나씩 뜯어볼게요 💡
- 게이트웨이 : 채널 객체는 그대로 옵니다. 대신 name이 ___hidden___ 이 되고, 민감한 필드는 null 처리되거나 줄어들어요. permission_overwrites에는 @everyone 역할의 VIEW_CHANNEL을 거부하는 항목 딱 하나만 남습니다.
- HTTP : GET /guilds/{guild.id}/channels 응답에서 그 채널들이 통째로 사라집니다. 즉 guild.channels(캐시)와 await guild.fetch_channels()(HTTP)의 개수가 달라져요.
- 난독화돼도 살아있는 필드 : id, type, position, parent_id 이 넷은 가려지지 않아요. 나머지 필드는 믿으면 안 됩니다.
- 인터랙션은 예외 : 슬래시 명령어 등 인터랙션 페이로드는 별도 경로로 만들어져서 난독화가 적용되지 않아요. 봇이 실제로 동작하는 채널 정보는 온전히 받습니다 👍
- 카테고리 : 하위 채널 중 하나라도 볼 수 있으면 그 카테고리는 보입니다.
- 음성 채널 : VIEW_CHANNEL이 없어도 접속 중이면 볼 수 있는 것으로 칩니다.

📌 내 봇, 영향받나요?
판단 기준은 아주 간단해요. 봇이 자기가 못 보는 채널의 정보를 읽고 있나요?
영향을 받는 봇은 이런 것들이에요 ⚠️
- 서버의 전체 채널 목록을 미러링·백업·감사하는 봇
- 모든 채널의 권한을 계산해서 리포트하는 봇
- 봇 자신의 접근 권한과 무관하게 서버 구조를 그려주는 봇
- 채널 개수를 세서 통계 내는 봇
반대로 봇이 권한 있는 채널에서만 동작한다면 아무 영향 없어요. 지금까지 시리즈에서 만든 환영 메시지(#20), 자동응답(#21), 자동완성(#22) 같은 기능들은 전부 안전합니다 😌
📌 준비물
- Python 3.8 이상 (예제는 3.9 이상 기준으로 작성했어요)
- discord.py 2.7.1 — 2026-08-18 기준 최신 버전이에요
- python-dotenv — 토큰을 .env로 분리하기 위해 필요해요
- 봇을 초대해 둔 테스트 서버 (관리자 권한이면 편해요)
pip install discord.py==2.7.1 python-dotenv프로젝트 폴더에 .env 파일을 만들고 토큰을 넣어주세요.
DISCORD_TOKEN=여기에_봇_토큰💡 .env는 꼭 .gitignore에 추가하세요. 깃허브에 토큰이 올라가면 디스코드가 자동으로 감지해서 토큰을 폐기해버려요 😱
.env
__pycache__/📌 1단계 - 개발자 포털에서 미리 켜기
11월까지 기다릴 필요 없어요. 지금 바로 난독화된 세상을 켜볼 수 있습니다.
- 디스코드 개발자 포털에 접속해요
- 내 애플리케이션을 선택하고 Overview → Bot 탭으로 들어가요
- Private Channel Obfuscation 토글을 켜요
- 봇을 재시작하면 끝! 이제 봇이 11/16 이후의 세상을 미리 살게 됩니다 🔮

하나씩 뜯어볼게요 💡
- 토글의 성격 : 디스코드가 테스트용으로 임시 제공하는 스위치예요. 11/16 이후에는 사라지고 전체 봇에 강제 적용됩니다.
- capabilities 방식 : 공식 문서에는 Identify 페이로드의 capabilities 필드에 1 << 15를 보내는 방법도 나와 있어요. 다만 discord.py 2.7.1은 IDENTIFY에 capabilities를 넣는 기능이 없어요. 라이브러리를 직접 뜯어고치는 게 아니라면 위의 포털 토글을 쓰는 게 정답입니다 ✅
- HTTP는 미리 못 켜요 : 다시 강조하지만 GET /guilds/{id}/channels의 채널 생략은 조기 옵트인이 없습니다. 11/16에 그냥 시작돼요.
⚠️ 헷갈리기 쉬운 두 숫자를 정리하고 갈게요. 1 << 15는 테스트 옵트인용 capability, 1 << 17은 채널에 붙는 CHANNEL_OBFUSCATED 플래그예요. 서로 다른 비트필드의 다른 값입니다!
📌 2단계 - 안 보이는 채널 판별하기
난독화 여부를 판별하는 공식 정답은 플래그 확인이에요.
CHANNEL_OBFUSCATED = 1 << 17 # 131072
def is_obfuscated(channel) -> bool:
"""게이트웨이로 받은 채널이 난독화됐는지 판별해요."""
flags = getattr(channel, "flags", None)
if flags is not None:
return bool(flags.value & CHANNEL_OBFUSCATED)
return False그런데 여기서 솔직하게 말씀드릴 게 있어요 😅
discord.py 2.7.1은 아직 이 플래그를 제대로 못 읽습니다. 직접 확인해봤는데, TextChannel·VoiceChannel·CategoryChannel에는 flags 속성 자체가 없어요. (ForumChannel과 Thread에만 있습니다.) ChannelFlags에도 channel_obfuscated 항목이 아직 추가되지 않았고요. 라이브러리 업데이트를 기다려야 하는 부분이에요.
그래서 지금 당장 쓸 수 있는 진짜 방법은 권한으로 판별하는 것입니다.
def can_view(channel, me) -> bool:
"""내 봇이 이 채널을 볼 수 있나요?"""
return channel.permissions_for(me).view_channel하나씩 뜯어볼게요 💡
- permissions_for(me) : 특정 멤버 기준으로 그 채널의 최종 권한을 계산해줘요. me에는 guild.me(서버 안의 내 봇)를 넣습니다.
- .view_channel : VIEW_CHANNEL 권한이에요. 이게 False면 11/16 이후 그 채널은 난독화됩니다. 난독화 여부와 결과가 정확히 일치해요.
- 왜 이게 더 안전한가 : 라이브러리 버전과 무관하고, 난독화가 켜지기 전에도 미리 결과를 알 수 있어요. 플래그 방식은 라이브러리가 지원하면 그때 켜지도록 getattr로 감싸두면 됩니다 👍
- name으로 판별하면 안 되는 이유 : 공식 문서가 name이나 다른 난독화 필드를 보고 판단하지 말라고 못 박았어요. 누군가 채널 이름을 진짜로 ___hidden___으로 지으면 어떻게 될까요? 😂
📌 3단계 - /채널점검 명령어 만들기
이제 내 서버에서 실제로 몇 개나 가려지는지 세어보는 슬래시 명령어를 만들어볼게요. 핵심은 게이트웨이 목록과 HTTP 목록을 나란히 비교하는 거예요.
@bot.tree.command(name="채널점검", description="봇이 못 보는 채널이 몇 개인지 점검해요")
async def channel_check(interaction: discord.Interaction):
guild = interaction.guild
if guild is None:
await interaction.response.send_message("서버 안에서 써주세요!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
me = guild.me
gateway_channels = guild.channels # 게이트웨이·캐시
http_channels = await guild.fetch_channels() # HTTP 요청
hidden = [c for c in gateway_channels if not can_view(c, me)]
only_gateway = {c.id for c in gateway_channels} - {c.id for c in http_channels}하나씩 뜯어볼게요 💡
- guild.channels : 게이트웨이로 받아 캐시에 쌓아둔 목록이에요. 난독화돼도 채널이 그대로 남아 있습니다.
- await guild.fetch_channels() : 매번 HTTP로 새로 받아오는 목록이에요. 내부적으로 GET /guilds/{id}/channels를 호출합니다. 11/16 이후 여기서만 채널이 사라져요.
- only_gateway : 두 집합의 차집합이에요. 11/16 전에는 0이 나오는 게 정상이고, 11/16 이후 이 숫자가 곧 가려진 채널 수가 됩니다 📊
- defer(ephemeral=True) : fetch_channels()는 네트워크 요청이라 3초를 넘길 수 있어요. 미리 "생각 중" 상태로 만들어두면 타임아웃을 피할 수 있습니다.
📌 전체 코드 (복붙 OK)
check_obfuscation.py 파일 하나로 돌아가는 점검 도구예요. 그대로 복사해서 쓰세요!
# discord.py 2.7.1 / Python 3.8+
# 디스코드 봇 채널 난독화(Channel Obfuscation) 점검 도구
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
if not TOKEN:
raise SystemExit("❌ .env 파일에 DISCORD_TOKEN 이 없어요!")
# Discord 공식 문서 기준 채널 플래그
CHANNEL_OBFUSCATED = 1 << 17 # 131072
HIDDEN_NAME = "___hidden___"
intents = discord.Intents.default() # guilds 인텐트만 있으면 충분해요
bot = commands.Bot(command_prefix="!", intents=intents)
def is_obfuscated(channel) -> bool:
"""CHANNEL_OBFUSCATED 플래그 확인 (라이브러리가 지원하면 자동으로 동작해요)."""
flags = getattr(channel, "flags", None)
if flags is not None:
return bool(flags.value & CHANNEL_OBFUSCATED)
return False
def can_view(channel, me) -> bool:
"""내 봇이 이 채널을 볼 수 있는지 권한으로 판별해요."""
return channel.permissions_for(me).view_channel
@bot.event
async def on_ready():
await bot.tree.sync()
print(f"✅ 로그인: {bot.user} / discord.py {discord.__version__}")
@bot.tree.command(name="채널점검", description="봇이 못 보는 채널이 몇 개인지 점검해요")
async def channel_check(interaction: discord.Interaction):
guild = interaction.guild
if guild is None:
await interaction.response.send_message("서버 안에서 써주세요!", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
me = guild.me
gateway_channels = guild.channels
http_channels = await guild.fetch_channels()
hidden = [c for c in gateway_channels if not can_view(c, me)]
flagged = [c for c in gateway_channels if is_obfuscated(c)]
renamed = [c for c in gateway_channels if c.name == HIDDEN_NAME]
only_gateway = {c.id for c in gateway_channels} - {c.id for c in http_channels}
embed = discord.Embed(
title="🔍 채널 난독화 점검 결과",
description="2026-11-16 변경에 우리 봇이 준비됐는지 확인해요.",
color=0x5865F2,
)
embed.add_field(name="게이트웨이 채널", value=f"{len(gateway_channels)}개", inline=True)
embed.add_field(name="HTTP 채널", value=f"{len(http_channels)}개", inline=True)
embed.add_field(name="HTTP에서 빠짐", value=f"{len(only_gateway)}개", inline=True)
embed.add_field(name="봇이 못 보는 채널", value=f"{len(hidden)}개", inline=True)
embed.add_field(name="OBFUSCATED 플래그", value=f"{len(flagged)}개", inline=True)
embed.add_field(name=f"이름이 {HIDDEN_NAME}", value=f"{len(renamed)}개", inline=True)
if hidden:
preview = "\n".join(f"• `{c.name}` · id `{c.id}` · {c.type}" for c in hidden[:10])
embed.add_field(name="못 보는 채널 미리보기", value=preview, inline=False)
embed.set_footer(text="게이트웨이는 가려서 보내고, HTTP는 아예 뺍니다")
await interaction.followup.send(embed=embed, ephemeral=True)
@bot.event
async def on_guild_channel_update(before, after):
"""봇이 채널을 다시 볼 수 있게 되면 여기로 알림이 와요."""
if before.name == HIDDEN_NAME and after.name != HIDDEN_NAME:
print(f"👀 채널이 다시 보이기 시작했어요: {after.name} ({after.id})")
bot.run(TOKEN)💡 #19편에서 만든 cogs/ 구조를 쓰고 계시다면, 위 명령어 부분만 떼어내 cogs/inspect.py로 옮기셔도 똑같이 동작해요!
📌 실행 결과
python check_obfuscation.py봇이 켜지면 서버에서 /채널점검 을 입력해보세요. 나만 보이는(ephemeral) 임베드로 결과가 옵니다.


제 테스트 서버에서는 이렇게 나왔어요.
- 개발자 포털 토글을 끈 상태 : 게이트웨이 9개 / HTTP 9개 / HTTP에서 빠짐 0개 / 봇이 못 보는 채널 1개
- 토글을 켠 상태 : 게이트웨이 9개 / HTTP 9개 / HTTP에서 빠짐 0개 / 이름이 ___hidden___ 1개
포인트가 보이시나요? 토글을 켜도 채널 개수는 안 줄어요. 게이트웨이는 채널을 계속 보내주니까요. 대신 이름이 ___hidden___으로 바뀌면서 정보가 사라집니다. 그리고 HTTP 쪽은 11/16이 되기 전까지 아무 변화가 없어요. 그날부터 HTTP에서 빠짐 숫자가 올라갑니다 📈
봇이 못 보는 채널 숫자가 0보다 크다면, 그 채널들이 11/16에 가려질 대상이에요. 그 채널들의 이름·주제를 쓰는 코드가 있는지 지금 확인해보세요!
만약에 hidden을 했는데 이름이 hidden이 0으로 나온다고 하면 _ < 언더바 개수 주의 해주세요! 디스코드는 언더바 3개씩 입니다! ___ < 언더바 3개 블로그에서는 붙은걸로 보여서 2개로 착각하는 경우가 있습니다 (제가... ㅋ.ㅋ)
📌 자주 나는 에러
1. TypeError: 'NoneType' object is not ... / 토큰이 None
- 원인 : .env 파일이 없거나, 파일 이름이 env.txt처럼 잘못됐거나, 스크립트를 다른 폴더에서 실행했어요.
- 해결 : .env가 check_obfuscation.py와 같은 폴더에 있는지 확인하고, 키 이름이 DISCORD_TOKEN인지 다시 보세요. 위 코드의 raise SystemExit이 미리 잡아줍니다.
2. AttributeError: 'TextChannel' object has no attribute 'flags'
- 원인 : discord.py 2.7.1의 텍스트·음성·카테고리 채널에는 flags 속성이 없어요.
- 해결 : channel.flags 를 직접 쓰지 말고, 위 코드처럼 getattr(channel, "flags", None) 로 감싸세요. 판별은 can_view() 로 하시면 됩니다.
3. discord.errors.NotFound: 404 Not Found (error code: 10062): Unknown interaction
- 원인 : 3초 안에 응답하지 못했어요. fetch_channels()가 느릴 때 발생합니다.
- 해결 : 무거운 작업 전에 await interaction.response.defer() 를 먼저 호출하세요. 그 뒤에는 interaction.followup.send() 로 보내야 합니다.
4. /채널점검 명령어가 서버에 안 떠요
- 원인 : 글로벌 동기화(bot.tree.sync())는 반영에 최대 1시간이 걸려요.
- 해결 : 테스트 중이라면 await bot.tree.sync(guild=discord.Object(id=서버ID)) 로 특정 서버에만 즉시 동기화하세요. 이 방법은 #3편에서 다뤘어요.
5. 봇이 채널을 다시 볼 수 있게 됐는데 캐시가 옛날 그대로예요
- 원인 : 권한이 생기는 순간 게이트웨이가 Channel Update 이벤트로 온전한 데이터를 다시 보내주는데, 그걸 처리하지 않았어요.
- 해결 : 위 전체 코드의 on_guild_channel_update 처럼 이벤트를 받아 캐시를 갱신하세요. 참고로 부모 카테고리도 함께 가려져 있었다면 카테고리에도 Channel Update가 따로 옵니다.
💬 오늘 꼭 기억할 3가지
- 게이트웨이는 가려서 보내고, HTTP는 아예 뺍니다. 이 비대칭이 전부예요.
- 판별은 name이 아니라 권한(또는 1 << 17 플래그)으로. 이름 보고 판단하면 언젠가 터집니다.
- 내 봇이 권한 있는 채널에서만 논다면 아무것도 안 해도 됩니다. 괜히 겁먹지 마세요 😌
📌 마무리
오늘은 새 기능 대신 11월 16일에 터질 수 있는 지뢰를 미리 밟아봤어요 💣
정리하면, 봇이 못 보는 채널은 게이트웨이에서 ___hidden___으로 가려지고 HTTP 목록에서는 사라집니다. 판별은 CHANNEL_OBFUSCATED(1 << 17) 플래그가 정석이지만, discord.py 2.7.1에서는 아직 못 읽으니 permissions_for().view_channel 로 확인하면 돼요. 그리고 개발자 포털의 Private Channel Obfuscation 토글로 지금 당장 미리 테스트할 수 있고요 ✅
아직 3개월 남았어요. 지금 /채널점검 한 번 돌려두면 11월에 마음이 아주 편해집니다 😎
다음 편에서는 파이썬 requests로 오픈 API 활용하기를 다뤄볼게요. 봇 밖으로 나가서 외부 데이터를 끌어오는 이야기예요. 기대해주세요! 🙌
궁금한 점이나 "우리 봇은 이런 구조인데 괜찮을까요?" 같은 질문은 댓글로 남겨주세요. 같이 확인해봐요 💬
📚 디스코드 봇 만들기 시리즈
- #1 ~ #16편 전체 보기 (카테고리)
- #17 - RAG로 문서 기반 답변봇 만들기
- #18 - 투표(Poll) 기능 만들기
- #19 - Cog로 코드 쪼개기
- #20 - 자동 환영 메시지·온보딩 만들기
- #21 - 키워드 자동응답·금지어 필터 만들기
- #22 - 슬래시 명령어 자동완성(Autocomplete) 만들기
- #23 - aiosqlite로 비동기 DB 제대로 설계하기
- #24 - 채널 난독화(Channel Obfuscation) 대응하기 ← 지금 이 글!
연재 · 32편 중 24번째
디스코드 봇 만들기
- #1디스코드 봇 만들기 - Slash Command 완전 정복!
- #2Slash Command에 옵션(Arguments) 추가하기!
- #3Slash Command에 서브 커맨드(Subcommand) 넣기!
- #4Slash Command로 임베드 전송하기!
- #5버튼(Button) 상호작용 구현하기!
- #6드롭다운(Select Menu) 메뉴 구현하기!
- #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)
- #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
- #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
- #10다국어(i18n) 지원하기! (파이썬 예제)
- #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
- #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
- #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
- #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
- #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)
- #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제)
- #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)
- #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제)
- #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)
- #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제)
- #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제)
- #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제)
- #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제)
- #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)지금 읽는 글
- #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제)
- #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제)
- #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제)
- #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제)
- #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제)
- #30투표·설문(Poll) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
- #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
