|
-
Feb 11th, 2022, 08:08 AM
#1
Thread Starter
Lively Member
[RESOLVED] How to convert this Python function to Vb.Net?
I'm Using an API example that transform a string into a dictionary and then calculate its md5. Unfortunately there is just a python example and I never developer in this language. So far I tried my best and I'll append what I did. Python code:
Code:
import hashlib
import json
def params_string_to_dict(params_str):
params = params_str.split("&")
params_dict = {}
for param in params:
key, value = param.split("=")
params_dict[key] = value
return params_dict
def params_dict_to_string(params_dict):
params_str = ""
items = params_dict.items()
for key, value in items:
params_str += key + '=' + value + '&'
return params_str[:-1]
def add_sign_to_params(params_dict, api, secret):
params_dict['api_key'] = api
params_str = json.dumps(params_dict, sort_keys=True, indent=4)
params_str = params_dict_to_string(json.loads(params_str))
params_str += '&secret_key=' + secret
hash_md5 = hashlib.md5(params_str.encode(encoding='utf-8'))
sign = hash_md5.hexdigest().upper()
params_dict['sign'] = sign
return params_dict
params_url="Api url"
params_str = "Api endpoint".replace(" ", "")
params_dict = params_string_to_dict(params_str)
#print(params_dict)
params_dict = add_sign_to_params( params_dict,"Key", "Secret Key")
#print(params_dict)
params_str = params_dict_to_string(params_dict)
print( params_url +"?" + params_str)
What I tried so far is:
Code:
Option Strict On
Imports System.Text
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Dim value() As String
Dim key() As String
Public Function params_string_to_dict(params_str As String)
Dim params() As String
params = params_str.Split(CChar("&"))
Dim params_dict As String
params_dict = Nothing
For Each param In params
value = param.Split(CChar("="))
key = param.Split(CChar("="))
params_dict{key} = value
Next
Return params_dict
End Function
Public Function params_dict_to_string(params_dict As String)
Dim params_str As String
params_str = ""
Dim items As String
items = params_dict.items()
For Each key And value in items
params_str += key & "=" & value & "&"
Next
Return params_str(-1)
End Function
Dim params_dict As String
Public Function add_sign_to_params(params_dict As String, api As String, secret As String)
Dim params_dict As String
params_dict("api_key") = api
Dim params_str As String
params_str = json.dumps(params_dict, sort_keys = True, indent = 4)
params_str = params_dict_to_string(json.loads(params_str))
params_str += "&secret_key=" + secret
Dim hashlibmd5(params_strencode(Encoding, As Object))
Dim hash_md5 As Integer
hashlibmd5(params_strencode(Encoding = "utf-8"))
hash_md5 = hashlibmd5(params_strencode(Encoding))
Dim sign As Integer
sign = hash_md5.hexdigest().upper()
params_dict("sign") = sign
Return params_dict
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim params_url As String = "https://api.hotbit.io/api/v1/balance.history"
Dim params_str As String = "API endpoint".Replace(" ", "")
Dim params_dict As String = params_string_to_dict(params_str)
Dim params_dict As String = add_sign_to_params(params_dict, "Key", "Secret")
Dim params_str As String = params_dict_to_string(params_dict)
Console.WriteLine(params_url + "?" + params_str)
End Sub
End Class
But I got some errors because some of the things are quite difficult for me to convert into vb.net For example, how can I use params_XXX without declaring them? Why is it using the word Json while basically I need to pass a String to it?
I got roughly 25 errors and most of them are in the Public Function add_sign_to_params . It is really difficult for me to convert these functions into vb.net as they might have different construction in python.
I am not asking for a ready code, I'm asking help to convert it step by step.
For example, I've already searched on google how to convert a string to md5, but this code is not just converti a string to md5, it is using kind of weird and complex ( to me ) structures like "sort_keys = True, indent = 4)"..
Thanks
-
Feb 11th, 2022, 09:23 AM
#2
Re: How to convert this Python function to Vb.Net?
The first two functions are easy:-
Code:
Private Function params_string_to_dict(ByVal params_str As String) As Dictionary(Of String, String)
Dim params = params_str.Split("&")
Dim d As New Dictionary(Of String, String)
For Each param In params
Dim kv = param.Split("=")
d.Add(kv(0), kv(1))
Next
Return d
End Function
Private Function params_dict_to_string(params_dict As Dictionary(Of String, String)) As String
Return String.Join("&", (From kv In params_dict Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
The last one is a bit tricky. I don't know what that sign stuff is about and I'm not sure .Net's MD5 classes even have that functionality used by the Python library. I'll have to research what that sign stuff is about so I get it right. .Net can create MD5 hashes though.
-
Feb 11th, 2022, 11:43 AM
#3
Re: How to convert this Python function to Vb.Net?
Ok. Here are all 3 functions converted:-
Code:
Private Function params_string_to_dict(ByVal params_str As String) As SortedDictionary(Of String, String)
Dim params = params_str.Split("&")
Dim d As New SortedDictionary(Of String, String)
For Each param In params
Dim kv = param.Split("=")
d.Add(kv(0), kv(1))
Next
Return d
End Function
Private Function params_dict_to_string(params_dict As SortedDictionary(Of String, String)) As String
Return String.Join("&", (From kv In params_dict Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
Private Function add_sign_to_params(ByVal params_dict As SortedDictionary(Of String, String), ByVal api As String, ByVal secret As String) As SortedDictionary(Of String, String)
Dim hash_md5 As Byte()
Dim md5 As MD5 = MD5.Create()
params_dict.Add("api_key", api)
params_dict.Add("secret_key", secret)
hash_md5 = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(params_dict_to_string(params_dict)))
params_dict.Add("sign", String.Concat(From b In hash_md5 Select b.ToString("X2")))
Return params_dict
End Function
I changed the implementation of the first two functions I posted previously. Here I use a SortedDictionary instead of a normal Dictionary because the third function performed some JSON conversion that sorted the keys so I opted to use a SortedDictionary specifically for that purpose.
Also, I didn't implement the JSON conversion found in the original code of the 3rd function because as far as I could tell it was pointless. It just converted the dictionary to a String and then converts it back to a dictionary again. I don't know why they did that but it seemed totally pointless to me.
I don't have your actual data, nor do I know what you're going to use this for so I cannot verify it's correctness but this is as faithful a translation I could produce without the ability to test it properly. Hopefully this works for you.
-
Feb 11th, 2022, 12:04 PM
#4
Re: How to convert this Python function to Vb.Net?
I tested it with some code based on how I think it's meant to work. I used your Button1_Click code as a base and did this:-
Code:
Dim params_url As String = "https://api.hotbit.io/api/v1/balance.history"
Dim params_str = "name = chistian & age = 16 & height = 43".Replace(" ", "")
Dim params_dict = params_string_to_dict(params_str)
params_dict = add_sign_to_params(params_dict, "Key", "Secret Key")
params_str = params_dict_to_string(params_dict)
Debug.WriteLine($"{params_url}?{params_str}")
And it output this:-
Code:
https://api.hotbit.io/api/v1/balance.history?age=16&api_key=Key&height=43&name=chistian&secret_key=Secret Key&sign=671059914232412C6EC5D6ACC5B3DF89
Again, I must reiterate, I have no idea if it will work for the actual thing it's meant to be used for. It appears correct here but I don't know if it actually is.
-
Feb 11th, 2022, 01:48 PM
#5
Thread Starter
Lively Member
Re: How to convert this Python function to Vb.Net?
Hi Niya,
First of all I have to say Thanks a Lot because you made a super great job! I don't know if you already knew some python, but to me it was quite impossible to understand how python works in order to translate it on vb.net.
So the output is almost correct, using those inputs:
Code:
Dim params_url As String = "https://api.hotbit.io/api/v1/order.pending"
Dim params_str = "market=KIBA/USDT&offset=0&limit=100".Replace(" ", "")
Dim params_dict = params_string_to_dict(params_str)
params_dict = add_sign_to_params(params_dict, "KEY", "SECRETKEY")
params_str = params_dict_to_string(params_dict)
TextBox1.Text = ($"{params_url}?{params_str}")
The output that it gives me is :
While I'm expecting this output:
So I don't need this in the output
and also I don't understand the reason why
market=KIBA/USDT&offset=0&limit=100
is reversed "&limit=100&market=KIBA/USDT&offset=0"
By the way, the md5 is right calculated.
Still many thanks
I appreciated the time you spent to help me
Update:
I've managed to remove the "secret_key=" from the output using
Code:
params_dict.Remove("secret_key")
I'm still don't really understand now how not to let it split the params_str
Last edited by matty95srk; Feb 11th, 2022 at 02:22 PM.
-
Feb 11th, 2022, 03:19 PM
#6
Re: How to convert this Python function to Vb.Net?
 Originally Posted by matty95srk
First of all I have to say Thanks a Lot because you made a super great job! I don't know if you already knew some python, but to me it was quite impossible to understand how python works in order to translate it on vb.net.
Yea, I learned some Python recently. My immediate future contains a lot of Python so I started learning it.
 Originally Posted by matty95srk
So I don't need this in the output
It seems I made a very slight mistake in my translation of the 3rd function. I didn't realize that the secret key wasn't actually added to the dictionary but it was hashed. Easy mistake to fix:-
Code:
Private Function add_sign_to_params(ByVal params_dict As SortedDictionary(Of String, String), ByVal api As String, ByVal secret As String) As SortedDictionary(Of String, String)
Dim hash_md5 As Byte()
Dim md5 As MD5 = MD5.Create()
params_dict.Add("api_key", api)
hash_md5 = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(params_dict_to_string(params_dict) & $"&secret_key={secret}"))
params_dict.Add("sign", String.Concat(From b In hash_md5 Select b.ToString("X2")))
Return params_dict
End Function
 Originally Posted by matty95srk
and also I don't understand the reason why is reversed "&limit=100&market=KIBA/USDT&offset=0"
My code works slightly different to the original implementation. The original implementation never sorts the Dictionary whereas mine does. I didn't think the order was important but I don't know for sure. It was just an educated guess. Is the order important?
 Originally Posted by matty95srk
I'm still don't really understand now how not to let it split the params_str
I don't understand what you mean by this.
-
Feb 11th, 2022, 04:13 PM
#7
Thread Starter
Lively Member
Re: How to convert this Python function to Vb.Net?
Thanks,
I mean that params_str
market=KIBA/USDT&offset=0&limit=100
should stai as it is, instead in your code output it is returning
Code:
https://api.hotbit.io/api/v1/order.pending?api_key=KEY&limit=100&market=KIBA/USDT&offset=0&sign=SECRET
You see? I'm looking for
this one
Code:
https://api.hotbit.io/api/v1/order.pending?api_key=KEY&market=KIBA/USDT&offset=0&limit=100&sign=SECRET
and not this one
Code:
https://api.hotbit.io/api/v1/order.pending?api_key=KEY&limit=100&market=KIBA/USDT&offset=0&sign=SECRET
The position is quite important.
Many thanks
-
Feb 11th, 2022, 05:26 PM
#8
Re: How to convert this Python function to Vb.Net?
I find it extremely strange that the order is important. I mean if the parameters are positional then what would be the point of referring to them by name.
Nonetheless, I reverted all the functions to use the normal Dictionary instead of a SortedDictionary and made some changes to the 3rd function. This version would produce the output exactly as you desired:-
Code:
Private Function params_string_to_dict(ByVal params_str As String) As Dictionary(Of String, String)
Dim params = params_str.Split("&")
Dim d As New Dictionary(Of String, String)
For Each param In params
Dim kv = param.Split("=")
d.Add(kv(0), kv(1))
Next
Return d
End Function
Private Function params_dict_to_string(params_dict As Dictionary(Of String, String)) As String
Return String.Join("&", (From kv In params_dict Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
Private Function add_sign_to_params(ByVal params_dict As Dictionary(Of String, String), ByVal api As String, ByVal secret As String) As Dictionary(Of String, String)
Dim hash_md5 As Byte()
Dim md5 As MD5 = MD5.Create()
Dim d As New Dictionary(Of String, String)
d.Add("api_key", api)
For Each kv In params_dict
d.Add(kv.Key, kv.Value)
Next
hash_md5 = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(params_dict_to_string(d) & $"&secret_key={secret}"))
d.Add("sign", String.Concat(From b In hash_md5 Select b.ToString("X2")))
Return d
End Function
-
Feb 11th, 2022, 06:03 PM
#9
Thread Starter
Lively Member
Re: How to convert this Python function to Vb.Net?
Hey Niya,
for how weird it looks, I can tell you I'm working on a super weird platform that require a weird kind of position or I won't be able to make the api working. .. The platform require a correct positioning of string in order to calculate the md5, after that it doesn't care anymore as long as I'm changing "secret_key" with "sign". Just to show you how weird it is look: https://github.com/hotbitex/hotbit.i...r/readme_en.md
and this too https://media.discordapp.net/attachm...18/unknown.png
By the way, unfortunately it was calculating correctly the md5 before.. Now it gives me a wrong md5, but fixed the param_str issue..
Would it be possible to have the same code of before that was working properly on the md5 but leaving the param_str "market=KIBA/USDT&offset=0&limit=100" as it is?
Many thanks
edit:
I've tried again your old code with SortedDictionary and I can confirm that code was calculating nice the md5. The problem is just params_str
To make things easier for you,
I'm sharing the code with real key and secret key( that are obviously disabled )
https://www.onlinegdb.com/xOgzxdD3Z
Last edited by matty95srk; Feb 11th, 2022 at 06:38 PM.
-
Feb 11th, 2022, 06:59 PM
#10
Re: How to convert this Python function to Vb.Net?
 Originally Posted by matty95srk
I am assuming the code here with they keys work and you want the VB.Net version to work exactly like this. Is this right?
-
Feb 11th, 2022, 07:01 PM
#11
Thread Starter
Lively Member
Re: How to convert this Python function to Vb.Net?
That code in there is calculating the exactly md5 and is showing the exactly position of strings, or the APi's won't work.. Hope it helps
-
Feb 11th, 2022, 08:09 PM
#12
Re: How to convert this Python function to Vb.Net?
 Originally Posted by matty95srk
Hope it helps
It helped immensely. It turns out that the hash must be generated on a sorted dictionary but the original dictionary, which is later used to generate the final param_str must remain unsorted:-
Code:
Private Function params_string_to_dict(ByVal params_str As String) As IDictionary(Of String, String)
Dim params = params_str.Split("&")
Dim d As New Dictionary(Of String, String)
For Each param In params
Dim kv = param.Split("=")
d.Add(kv(0), kv(1))
Next
Return d
End Function
Private Function params_dict_to_string(params_dict As IDictionary(Of String, String)) As String
Return String.Join("&", (From kv In params_dict Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
Private Function add_sign_to_params(ByVal params_dict As IDictionary(Of String, String), ByVal api As String, ByVal secret As String) As IDictionary(Of String, String)
Dim hash_md5 As Byte()
Dim md5 As MD5 = MD5.Create()
params_dict.Add("api_key", api)
hash_md5 = md5.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(
params_dict_to_string(
New SortedDictionary(Of String, String)(params_dict)) & $"&secret_key={secret}"))
params_dict.Add("sign", String.Concat(From b In hash_md5 Select b.ToString("X2")))
Return params_dict
End Function
I had to make changes to all 3 functions to facilitate the necessary changes to the output.
-
Feb 11th, 2022, 08:20 PM
#13
Thread Starter
Lively Member
Re: How to convert this Python function to Vb.Net?
That's perfect it works like a charm!!!
I'm seriously grateful for the help you gave me, patience, professionality you had with me. Without you I would have been completely lost! it was really a difficult task. I wish you good luck with your immediate future with Python, this was already a good sign that you will success with it.
Thanks,
Mattia.
Last edited by matty95srk; Feb 12th, 2022 at 05:43 AM.
-
Feb 11th, 2022, 08:52 PM
#14
Re: [RESOLVED] How to convert this Python function to Vb.Net?
No problem and thank you
-
Feb 12th, 2022, 07:41 AM
#15
Re: [RESOLVED] How to convert this Python function to Vb.Net?
I don't know if you're still around but when I woke up this morning and looked at this thread again, it occured to me that since we now have all the necessary information, this entire thing could be re-written in a more .Net way. That is to say, it could be written to resemble something we would normally see in .Net. I spent about 10 or so minutes doing just that and came up with this:-
Code:
Imports System.Security.Cryptography
Public Class HotbitAPI
Private _params As New Dictionary(Of String, String)
Public Property APIKey As String
Public Property SecretKey As String
Public Property URL As String
Public Sub New()
End Sub
Public Sub New(ByVal apiKey As String, ByVal secretKey As String)
Me.APIKey = apiKey
Me.SecretKey = secretKey
End Sub
Public Sub ClearParameters()
_params = New Dictionary(Of String, String)
End Sub
Public Sub SetParameter(ByVal pName As String, ByVal pValue As String)
If _params.ContainsKey(pName) Then
_params.Item(pName) = pValue
Else
_params.Add(pName, pValue)
End If
End Sub
Public Function GenerateRequestURL() As String
Dim paramList = _params.ToList
Dim md5HashFunc As MD5 = MD5.Create()
Dim sign As String
paramList.Add(New KeyValuePair(Of String, String)("api_key", Me.APIKey))
sign = ToHexString(md5HashFunc.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(
ParamsToString(From p In paramList Order By p.Key) & $"&secret_key={Me.SecretKey}")))
paramList.Add(New KeyValuePair(Of String, String)("sign", sign))
Return $"{Me.URL}?{ParamsToString(paramList)}"
End Function
Private Function ToHexString(ByVal data As Byte()) As String
Return String.Concat((From b In data Select b.ToString("X2")))
End Function
Private Function ParamsToString(ByVal params As IEnumerable(Of KeyValuePair(Of String, String))) As String
Return String.Join("&", (From kv In params Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
End Class
The above is a .Net class that does the same thing but in a way that would be more familiar to VB.Net programmers. You can use this class like this:-
Code:
Dim api As New HotbitAPI("dedc2315-1842-0335-25f209dded3f93f9", "57c9a8fdca1e01870ae09a8b2c93b20e")
api.URL = "https://api.hotbit.io/api/v1/order.pending"
'These can be set in any order
api.SetParameter("market", "KIBA/USDT")
api.SetParameter("offset", "0")
api.SetParameter("limit", "100")
Debug.WriteLine(api.GenerateRequestURL())
Also, I believe I was right the first time when I said that the URL should be able to tolerate parameters in any order. I am familiar with REST APIs and I've never seen one where the order of named parameters in the URL request matters. That would be insane because the whole point of naming them is so that they could be referenced in any order.
The order of the parameters only matter when generating the MD5 hash. If the order was wrong when generating the hash, the hash would be wrong and thus the call would fail. This is what led you to believe order mattered in the URL. It doesn't, it only matters when generating the MD5 hash. You should be able to call SetParameter in any order without it causing any problems. I made sure that the class internally generates the sign key correctly regardless of what order the parameters were added through SetParameter.
Also, I'm sure of this because the original Python code never actually makes any attempt to sort the parameters when generating the final URL. It only performed a sort when generating the hash. It's these two lines of code here:-
Code:
params_str = json.dumps(params_dict, sort_keys=True, indent=4)
params_str = params_dict_to_string(json.loads(params_str))
Those two lines perform the sort on the parameter String which is used to generate the hash but if you scroll down toward the end of the function, it just adds the sign to the original unsorted dictionary of parameters and returns that dictionary. That unsorted dictionary of parameters is then used to generate the final URL.
-
Feb 14th, 2022, 09:18 AM
#16
Re: [RESOLVED] How to convert this Python function to Vb.Net?
I did a bit of exploring of the GitHub link you posted and it occurred to me that all of this can be generalized even further to handle all of Hotbit's APIs in a very easy to use manner. There was enough information in that GitHub repo to give a clear picture of how to use that API. Here is a modified version of the class from my previous post that can be used for any function of the REST API:-
Code:
Imports System.Security.Cryptography
Public Class HotbitAPI
Private Const URLROOT As String = "https://api.hotbit.io/api/v1"
Private _params As New Dictionary(Of String, String)
Public Property APIKey As String
Public Property SecretKey As String
Public Property APIFunction As String
Public Sub New()
End Sub
Public Sub New(ByVal apiKey As String, ByVal secretKey As String)
Me.APIKey = apiKey
Me.SecretKey = secretKey
End Sub
Public Sub ClearParameters()
_params = New Dictionary(Of String, String)
End Sub
Public Sub AddParameter(ByVal pName As String, ByVal pValue As String)
If _params.ContainsKey(pName) Then
_params.Item(pName) = pValue
Else
_params.Add(pName, pValue)
End If
End Sub
Public Sub SetParameters(ParamArray params As String())
_params = New Dictionary(Of String, String)
For Each p In params
Dim kv = p.Split("=")
_params.Add(kv(0).Trim, kv(1).Trim)
Next
End Sub
Public Function GenerateRequestURL() As String
If _params.Count = 0 Then
Return $"{URLROOT}/{Me.APIFunction}"
Else
Return $"{URLROOT}/{Me.APIFunction}?{ParamsToString(_params.ToArray)}"
End If
End Function
Public Function GenerateSignedRequestURL() As String
Dim paramList = _params.ToList
Dim md5HashFunc As MD5 = MD5.Create()
Dim sign As String
paramList.Add(New KeyValuePair(Of String, String)("api_key", Me.APIKey))
sign = ToHexString(md5HashFunc.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(
ParamsToString(From p In paramList Order By p.Key) & $"&secret_key={Me.SecretKey}")))
paramList.Add(New KeyValuePair(Of String, String)("sign", sign))
Return $"{URLROOT}/{Me.APIFunction}?{ParamsToString(paramList)}"
End Function
Private Function ToHexString(ByVal data As Byte()) As String
Return String.Concat((From b In data Select b.ToString("X2")))
End Function
Private Function ParamsToString(ByVal params As IEnumerable(Of KeyValuePair(Of String, String))) As String
Return String.Join("&", (From kv In params Select String.Join("=", kv.Key, kv.Value)).ToArray)
End Function
End Class
Here is a reworked version of the original sample you posted that uses this class:-
Code:
Dim api As New HotbitAPI("dedc2315-1842-0335-25f209dded3f93f9", "57c9a8fdca1e01870ae09a8b2c93b20e")
'Select the API function we want to use
api.APIFunction = "order.pending"
'These can be set in any order
api.SetParameters("market = KIBA/USDT", "offset = 0", "limit = 100")
'You can do it this way as well
'===============================================
'api.AddParameter("market", "KIBA/USDT")
'api.AddParameter("offset", "0")
'api.AddParameter("limit", "100")
'===============================================
Debug.WriteLine(api.GenerateSignedRequestURL())
Just for fun I did one for the order.cancel function documented here. Here is the code to generate the URL:-
Code:
Dim api As New HotbitAPI("dedc2315-1842-0335-25f209dded3f93f9", "57c9a8fdca1e01870ae09a8b2c93b20e")
'Select the order.canel function
api.APIFunction = "order.cancel"
api.SetParameters("market = BTC/USDT", "order_id = 1")
Debug.WriteLine(api.GenerateSignedRequestURL())
I also noticed that not all of the functions require a hashed signature of the secret key and the API key. The class can also handle those. For example, the order.book function doesn't require a signature or API key. That function is documented here. Here is the sample code:-
Code:
Dim api As New HotbitAPI("dedc2315-1842-0335-25f209dded3f93f9", "57c9a8fdca1e01870ae09a8b2c93b20e")
'Select the order.book function
api.APIFunction = "order.book"
api.SetParameters("market = ETH/BTC", "side = 1", "offset = 0", "limit = 10")
'This function doesn't require a signature so we dont use GenerateSignedRequestURL.
'We use GenerateRequestURL to generate an unsigned URL
Debug.WriteLine(api.GenerateRequestURL())
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|