Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
小白菜888
Obs Studio
提交
a88b4402
O
Obs Studio
项目概览
小白菜888
/
Obs Studio
通知
4
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
O
Obs Studio
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
a88b4402
编写于
2月 06, 2019
作者:
J
jp9000
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
UI: Add Twitch integration
上级
4710c042
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
484 addition
and
0 deletion
+484
-0
UI/CMakeLists.txt
UI/CMakeLists.txt
+19
-0
UI/auth-twitch.cpp
UI/auth-twitch.cpp
+409
-0
UI/auth-twitch.hpp
UI/auth-twitch.hpp
+46
-0
UI/data/locale/en-US.ini
UI/data/locale/en-US.ini
+2
-0
UI/ui-config.h.in
UI/ui-config.h.in
+4
-0
UI/window-basic-main.cpp
UI/window-basic-main.cpp
+4
-0
未找到文件。
UI/CMakeLists.txt
浏览文件 @
a88b4402
...
...
@@ -19,6 +19,16 @@ project(obs)
set
(
DISABLE_UPDATE_MODULE TRUE CACHE BOOL
"Disables building the update module"
)
if
(
NOT DEFINED TWITCH_CLIENTID OR
"
${
TWITCH_CLIENTID
}
"
STREQUAL
""
OR
NOT DEFINED TWITCH_HASH OR
"
${
TWITCH_HASH
}
"
STREQUAL
""
OR
NOT BROWSER_AVAILABLE_INTERNAL
)
set
(
TWITCH_ENABLED FALSE
)
set
(
TWITCH_CLIENTID
""
)
set
(
TWITCH_HASH
"0"
)
else
()
set
(
TWITCH_ENABLED TRUE
)
endif
()
if
(
NOT DEFINED MIXER_CLIENTID OR
"
${
MIXER_CLIENTID
}
"
STREQUAL
""
OR
NOT DEFINED MIXER_HASH OR
"
${
MIXER_HASH
}
"
STREQUAL
""
OR
NOT BROWSER_AVAILABLE_INTERNAL
)
...
...
@@ -130,6 +140,15 @@ if(BROWSER_AVAILABLE_INTERNAL)
auth-oauth.hpp
)
if
(
TWITCH_ENABLED
)
list
(
APPEND obs_PLATFORM_SOURCES
auth-twitch.cpp
)
list
(
APPEND obs_PLATFORM_HEADERS
auth-twitch.hpp
)
endif
()
if
(
MIXER_ENABLED
)
list
(
APPEND obs_PLATFORM_SOURCES
auth-mixer.cpp
...
...
UI/auth-twitch.cpp
0 → 100644
浏览文件 @
a88b4402
#include "auth-twitch.hpp"
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <qt-wrappers.hpp>
#include <obs-app.hpp>
#include "window-basic-main.hpp"
#include "remote-text.hpp"
#include <json11.hpp>
#include "ui-config.h"
#include "obf.h"
using
namespace
json11
;
#include <browser-panel.hpp>
extern
QCef
*
cef
;
extern
QCefCookieManager
*
panel_cookies
;
/* ------------------------------------------------------------------------- */
#define TWITCH_AUTH_URL \
"https://obsproject.com/app-auth/twitch?action=redirect"
#define TWITCH_TOKEN_URL \
"https://obsproject.com/app-auth/twitch-token"
#define ACCEPT_HEADER \
"Accept: application/vnd.twitchtv.v5+json"
#define TWITCH_SCOPE_VERSION 1
static
Auth
::
Def
twitchDef
=
{
"Twitch"
,
Auth
::
Type
::
OAuth_StreamKey
};
/* ------------------------------------------------------------------------- */
TwitchAuth
::
TwitchAuth
(
const
Def
&
d
)
:
OAuthStreamKey
(
d
)
{
cef
->
add_popup_whitelist_url
(
"https://twitch.tv/popout/frankerfacez/chat?ffz-settings"
,
this
);
uiLoadTimer
.
setSingleShot
(
true
);
uiLoadTimer
.
setInterval
(
500
);
connect
(
&
uiLoadTimer
,
&
QTimer
::
timeout
,
this
,
&
TwitchAuth
::
TryLoadSecondaryUIPanes
);
}
bool
TwitchAuth
::
GetChannelInfo
()
try
{
std
::
string
client_id
=
TWITCH_CLIENTID
;
deobfuscate_str
(
&
client_id
[
0
],
TWITCH_HASH
);
if
(
!
GetToken
(
TWITCH_TOKEN_URL
,
client_id
,
TWITCH_SCOPE_VERSION
))
return
false
;
if
(
token
.
empty
())
return
false
;
if
(
!
key_
.
empty
())
return
true
;
std
::
string
auth
;
auth
+=
"Authorization: OAuth "
;
auth
+=
token
;
std
::
vector
<
std
::
string
>
headers
;
headers
.
push_back
(
std
::
string
(
"Client-ID: "
)
+
client_id
);
headers
.
push_back
(
ACCEPT_HEADER
);
headers
.
push_back
(
std
::
move
(
auth
));
std
::
string
output
;
std
::
string
error
;
bool
success
=
false
;
auto
func
=
[
&
]
()
{
success
=
GetRemoteFile
(
"https://api.twitch.tv/kraken/channel"
,
output
,
error
,
nullptr
,
"application/json"
,
nullptr
,
headers
,
nullptr
,
5
);
};
ExecuteFuncSafeBlockMsgBox
(
func
,
QTStr
(
"Auth.LoadingChannel.Title"
),
QTStr
(
"Auth.LoadingChannel.Text"
).
arg
(
service
()));
if
(
!
success
||
output
.
empty
())
throw
ErrorInfo
(
"Failed to get text from remote"
,
error
);
Json
json
=
Json
::
parse
(
output
,
error
);
if
(
!
error
.
empty
())
throw
ErrorInfo
(
"Failed to parse json"
,
error
);
error
=
json
[
"error"
].
string_value
();
if
(
!
error
.
empty
())
throw
ErrorInfo
(
error
,
json
[
"error_description"
].
string_value
());
name
=
json
[
"name"
].
string_value
();
key_
=
json
[
"stream_key"
].
string_value
();
return
true
;
}
catch
(
ErrorInfo
info
)
{
QString
title
=
QTStr
(
"Auth.ChannelFailure.Title"
);
QString
text
=
QTStr
(
"Auth.ChannelFailure.Text"
)
.
arg
(
service
(),
info
.
message
.
c_str
(),
info
.
error
.
c_str
());
QMessageBox
::
warning
(
OBSBasic
::
Get
(),
title
,
text
);
blog
(
LOG_WARNING
,
"%s: %s: %s"
,
__FUNCTION__
,
info
.
message
.
c_str
(),
info
.
error
.
c_str
());
return
false
;
}
void
TwitchAuth
::
SaveInternal
()
{
OBSBasic
*
main
=
OBSBasic
::
Get
();
config_set_string
(
main
->
Config
(),
service
(),
"Name"
,
name
.
c_str
());
if
(
uiLoaded
)
{
config_set_string
(
main
->
Config
(),
service
(),
"DockState"
,
main
->
saveState
().
toBase64
().
constData
());
}
OAuthStreamKey
::
SaveInternal
();
}
static
inline
std
::
string
get_config_str
(
OBSBasic
*
main
,
const
char
*
section
,
const
char
*
name
)
{
const
char
*
val
=
config_get_string
(
main
->
Config
(),
section
,
name
);
return
val
?
val
:
""
;
}
bool
TwitchAuth
::
LoadInternal
()
{
OBSBasic
*
main
=
OBSBasic
::
Get
();
name
=
get_config_str
(
main
,
service
(),
"Name"
);
firstLoad
=
false
;
return
OAuthStreamKey
::
LoadInternal
();
}
class
TwitchWidget
:
public
QDockWidget
{
public:
inline
TwitchWidget
()
:
QDockWidget
()
{}
QScopedPointer
<
QCefWidget
>
widget
;
inline
void
SetWidget
(
QCefWidget
*
widget_
)
{
setWidget
(
widget_
);
widget
.
reset
(
widget_
);
}
};
static
const
char
*
ffz_script
=
"\
var ffz = document.createElement('script');\
ffz.setAttribute('src','https://cdn.frankerfacez.com/script/script.min.js');\
document.head.appendChild(ffz);"
;
static
const
char
*
bttv_script
=
"\
localStorage.setItem('bttv_darkenedMode', true);\
var bttv = document.createElement('script');\
bttv.setAttribute('src','https://cdn.betterttv.net/betterttv.js');\
document.head.appendChild(bttv);"
;
static
const
char
*
referrer_script1
=
"\
Object.defineProperty(document, 'referrer', {get : function() { return '"
;
static
const
char
*
referrer_script2
=
"'; }});"
;
void
TwitchAuth
::
LoadUI
()
{
if
(
uiLoaded
)
return
;
if
(
!
GetChannelInfo
())
return
;
OBSBasic
::
InitBrowserPanelSafeBlock
(
true
);
OBSBasic
*
main
=
OBSBasic
::
Get
();
QCefWidget
*
browser
;
std
::
string
url
;
std
::
string
script
;
/* ----------------------------------- */
url
=
"https://www.twitch.tv/popout/"
;
url
+=
name
;
url
+=
"/chat"
;
QSize
size
=
main
->
frameSize
();
QPoint
pos
=
main
->
pos
();
chat
.
reset
(
new
TwitchWidget
());
chat
->
setObjectName
(
"twitchChat"
);
chat
->
resize
(
300
,
600
);
chat
->
setMinimumSize
(
200
,
300
);
chat
->
setWindowTitle
(
QTStr
(
"Auth.Chat"
));
chat
->
setAllowedAreas
(
Qt
::
AllDockWidgetAreas
);
browser
=
cef
->
create_widget
(
nullptr
,
url
,
panel_cookies
);
chat
->
SetWidget
(
browser
);
script
=
bttv_script
;
script
+=
ffz_script
;
browser
->
setStartupScript
(
script
);
main
->
addDockWidget
(
Qt
::
RightDockWidgetArea
,
chat
.
data
());
chatMenu
.
reset
(
main
->
AddDockWidget
(
chat
.
data
()));
/* ----------------------------------- */
chat
->
setFloating
(
true
);
chat
->
move
(
pos
.
x
()
+
size
.
width
()
-
chat
->
width
()
-
50
,
pos
.
y
()
+
50
);
if
(
firstLoad
)
{
chat
->
setVisible
(
true
);
}
else
{
const
char
*
dockStateStr
=
config_get_string
(
main
->
Config
(),
service
(),
"DockState"
);
QByteArray
dockState
=
QByteArray
::
fromBase64
(
QByteArray
(
dockStateStr
));
main
->
restoreState
(
dockState
);
}
TryLoadSecondaryUIPanes
();
uiLoaded
=
true
;
}
void
TwitchAuth
::
LoadSecondaryUIPanes
()
{
OBSBasic
*
main
=
OBSBasic
::
Get
();
QCefWidget
*
browser
;
std
::
string
url
;
std
::
string
script
;
QPoint
pos
=
main
->
pos
();
script
=
"localStorage.setItem('twilight.theme', 1);"
;
script
+=
referrer_script1
;
script
+=
"https://www.twitch.tv/"
;
script
+=
name
;
script
+=
"/dashboard/live"
;
script
+=
referrer_script2
;
script
+=
bttv_script
;
script
+=
ffz_script
;
/* ----------------------------------- */
url
=
"https://www.twitch.tv/popout/"
;
url
+=
name
;
url
+=
"/dashboard/live/stream-info"
;
info
.
reset
(
new
TwitchWidget
());
info
->
setObjectName
(
"twitchInfo"
);
info
->
resize
(
300
,
650
);
info
->
setMinimumSize
(
200
,
300
);
info
->
setWindowTitle
(
QTStr
(
"Auth.StreamInfo"
));
info
->
setAllowedAreas
(
Qt
::
AllDockWidgetAreas
);
browser
=
cef
->
create_widget
(
nullptr
,
url
,
panel_cookies
);
info
->
SetWidget
(
browser
);
browser
->
setStartupScript
(
script
);
main
->
addDockWidget
(
Qt
::
RightDockWidgetArea
,
info
.
data
());
infoMenu
.
reset
(
main
->
AddDockWidget
(
info
.
data
()));
/* ----------------------------------- */
url
=
"https://www.twitch.tv/popout/"
;
url
+=
name
;
url
+=
"/dashboard/live/stats"
;
stat
.
reset
(
new
TwitchWidget
());
stat
->
setObjectName
(
"twitchStats"
);
stat
->
resize
(
200
,
200
);
stat
->
setMinimumSize
(
200
,
200
);
stat
->
setWindowTitle
(
QTStr
(
"TwitchAuth.Stats"
));
stat
->
setAllowedAreas
(
Qt
::
AllDockWidgetAreas
);
browser
=
cef
->
create_widget
(
nullptr
,
url
,
panel_cookies
);
stat
->
SetWidget
(
browser
);
browser
->
setStartupScript
(
script
);
main
->
addDockWidget
(
Qt
::
RightDockWidgetArea
,
stat
.
data
());
statMenu
.
reset
(
main
->
AddDockWidget
(
stat
.
data
()));
/* ----------------------------------- */
info
->
setFloating
(
true
);
stat
->
setFloating
(
true
);
info
->
move
(
pos
.
x
()
+
50
,
pos
.
y
()
+
50
);
if
(
firstLoad
)
{
info
->
setVisible
(
true
);
stat
->
setVisible
(
false
);
}
else
{
const
char
*
dockStateStr
=
config_get_string
(
main
->
Config
(),
service
(),
"DockState"
);
QByteArray
dockState
=
QByteArray
::
fromBase64
(
QByteArray
(
dockStateStr
));
main
->
restoreState
(
dockState
);
}
}
/* Twitch.tv has an OAuth for itself. If we try to load multiple panel pages
* at once before it's OAuth'ed itself, they will all try to perform the auth
* process at the same time, get their own request codes, and only the last
* code will be valid -- so one or more panels are guaranteed to fail.
*
* To solve this, we want to load just one panel first (the chat), and then all
* subsequent panels should only be loaded once we know that Twitch has auth'ed
* itself (if the cookie "auth-token" exists for twitch.tv).
*
* This is annoying to deal with. */
void
TwitchAuth
::
TryLoadSecondaryUIPanes
()
{
QPointer
<
TwitchAuth
>
this_
=
this
;
auto
cb
=
[
this_
]
(
bool
found
)
{
if
(
!
this_
)
{
return
;
}
if
(
!
found
)
{
QMetaObject
::
invokeMethod
(
&
this_
->
uiLoadTimer
,
"start"
);
}
else
{
QMetaObject
::
invokeMethod
(
this_
,
"LoadSecondaryUIPanes"
);
}
};
panel_cookies
->
CheckForCookie
(
"https://www.twitch.tv"
,
"auth-token"
,
cb
);
}
bool
TwitchAuth
::
RetryLogin
()
{
OAuthLogin
login
(
OBSBasic
::
Get
(),
TWITCH_AUTH_URL
,
false
);
if
(
login
.
exec
()
==
QDialog
::
Rejected
)
{
return
false
;
}
std
::
shared_ptr
<
TwitchAuth
>
auth
=
std
::
make_shared
<
TwitchAuth
>
(
twitchDef
);
std
::
string
client_id
=
TWITCH_CLIENTID
;
deobfuscate_str
(
&
client_id
[
0
],
TWITCH_HASH
);
return
GetToken
(
TWITCH_TOKEN_URL
,
client_id
,
TWITCH_SCOPE_VERSION
,
QT_TO_UTF8
(
login
.
GetCode
()),
true
);
}
std
::
shared_ptr
<
Auth
>
TwitchAuth
::
Login
(
QWidget
*
parent
)
{
OAuthLogin
login
(
parent
,
TWITCH_AUTH_URL
,
false
);
if
(
login
.
exec
()
==
QDialog
::
Rejected
)
{
return
nullptr
;
}
std
::
shared_ptr
<
TwitchAuth
>
auth
=
std
::
make_shared
<
TwitchAuth
>
(
twitchDef
);
std
::
string
client_id
=
TWITCH_CLIENTID
;
deobfuscate_str
(
&
client_id
[
0
],
TWITCH_HASH
);
if
(
!
auth
->
GetToken
(
TWITCH_TOKEN_URL
,
client_id
,
TWITCH_SCOPE_VERSION
,
QT_TO_UTF8
(
login
.
GetCode
())))
{
return
nullptr
;
}
std
::
string
error
;
if
(
auth
->
GetChannelInfo
())
{
return
auth
;
}
return
nullptr
;
}
static
std
::
shared_ptr
<
Auth
>
CreateTwitchAuth
()
{
return
std
::
make_shared
<
TwitchAuth
>
(
twitchDef
);
}
static
void
DeleteCookies
()
{
if
(
panel_cookies
)
panel_cookies
->
DeleteCookies
(
"twitch.tv"
,
std
::
string
());
}
void
RegisterTwitchAuth
()
{
OAuth
::
RegisterOAuth
(
twitchDef
,
CreateTwitchAuth
,
TwitchAuth
::
Login
,
DeleteCookies
);
}
UI/auth-twitch.hpp
0 → 100644
浏览文件 @
a88b4402
#pragma once
#include <QDialog>
#include <QTimer>
#include <string>
#include <memory>
#include "auth-oauth.hpp"
class
TwitchWidget
;
class
TwitchAuth
:
public
OAuthStreamKey
{
Q_OBJECT
friend
class
TwitchLogin
;
QSharedPointer
<
TwitchWidget
>
chat
;
QSharedPointer
<
TwitchWidget
>
info
;
QSharedPointer
<
TwitchWidget
>
stat
;
QSharedPointer
<
QAction
>
chatMenu
;
QSharedPointer
<
QAction
>
infoMenu
;
QSharedPointer
<
QAction
>
statMenu
;
bool
uiLoaded
=
false
;
std
::
string
name
;
virtual
bool
RetryLogin
()
override
;
virtual
void
SaveInternal
()
override
;
virtual
bool
LoadInternal
()
override
;
bool
GetChannelInfo
();
virtual
void
LoadUI
()
override
;
public:
TwitchAuth
(
const
Def
&
d
);
static
std
::
shared_ptr
<
Auth
>
Login
(
QWidget
*
parent
);
QTimer
uiLoadTimer
;
public
slots
:
void
TryLoadSecondaryUIPanes
();
void
LoadSecondaryUIPanes
();
};
UI/data/locale/en-US.ini
浏览文件 @
a88b4402
...
...
@@ -103,6 +103,8 @@ Auth.LoadingChannel.Text="Loading channel information for %1, please wait.."
Auth.ChannelFailure.Title
=
"Failed to load channel"
Auth.ChannelFailure.Text
=
"Failed to load channel information for %1
\n\n
%2: %3"
Auth.Chat
=
"Chat"
Auth.StreamInfo
=
"Stream Information"
TwitchAuth.Stats
=
"Twitch Stats"
# copy filters
Copy.Filters
=
"Copy Filters"
...
...
UI/ui-config.h.in
浏览文件 @
a88b4402
...
...
@@ -16,6 +16,10 @@
#define OFF 0
#endif
#define TWITCH_ENABLED @TWITCH_ENABLED@
#define TWITCH_CLIENTID "@TWITCH_CLIENTID@"
#define TWITCH_HASH 0x@TWITCH_HASH@
#define MIXER_ENABLED @MIXER_ENABLED@
#define MIXER_CLIENTID "@MIXER_CLIENTID@"
#define MIXER_HASH 0x@MIXER_HASH@
UI/window-basic-main.cpp
浏览文件 @
a88b4402
...
...
@@ -191,6 +191,7 @@ void assignDockToggle(QDockWidget *dock, QAction *action)
handleMenuToggle
);
}
extern
void
RegisterTwitchAuth
();
extern
void
RegisterMixerAuth
();
OBSBasic
::
OBSBasic
(
QWidget
*
parent
)
...
...
@@ -199,6 +200,9 @@ OBSBasic::OBSBasic(QWidget *parent)
{
setAttribute
(
Qt
::
WA_NativeWindow
);
#if TWITCH_ENABLED
RegisterTwitchAuth
();
#endif
#if MIXER_ENABLED
RegisterMixerAuth
();
#endif
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录